Taking Laziness Seriously

Or you can call this post "Going nuts with shortcuts". A developer need to have a degree of healthy laziness, Neal Ford wrote a book about it if you're interested. Part of this laziness is expressed in the refusal of writing the same command line twice, Aravind S.V. (my colleague at TW and friend) is a master in making/coding shortcuts and aliases so he doesn't type a long, error prone, command line more than the first time. So, if you use bash or other shell daily here's some of my current ones.

My new NFS is called Dropbox

Yup, I'm old enough to be an old NFS user. A good thing is to keep you files, specially ad hoc scripts available all the time (that's what I did using NFS). Using a USB to copy them every time is a hassle, I confess that, before Dropbox, I just stopped using a lot of my custom scripts and settings files like .vimrc because the lack of a fast way to deploy them. But now is possible to store these files in Dropbox and keep them around. I just have to do this in my local profile (~/.bash_profile):

  bash_load_root=~/Dropbox/
  . ${bash_load_root}/load_bash

Different settings for each machine?

In my case I have two computers that I usually work on, a Dell laptop and a Mac laptop, they are a bit different specially the folders structure. Still, having a profile for each machine (and maybe duplicate settings) is not a good idea. The trick is using a folder with the specific settings by machine that will override the generic ones if necessary. Here how it's coded the load of my settings:

  machine=${HOSTNAME:-$(hostname -s)}

  loadtypes="variables profile functions aliases"

  for type in ${loadtypes}; do
      file="bash_${type}"
      [ -f ${bash_load_root}/${file} ] && . ${bash_load_root}/${file}
      [ -f ${bash_load_root}/${machine}/${file} ] && . ${bash_load_root}/${machine}/${file}
  done

This script loads in order (generic to specific) settings according to the machine I'm on. Note that bash_variables, bash_profile, bash_functions and bash_aliases are loaded in this order so that a generic function has the right context as a specific one (variables set first).

Now we have all that is needed to load the files, arguments, aliases, paths and such. It's up to you to code them as soon you caught yourself typing the same command over and over again. But first how about check what command you type the most? Aravind S.V. came with this simple history filtering that shows what you've been typing in the shell lately grouped by duplicates:

   history | sed 's/^[[:space:]]*[0-9]*[[:space:]]*//' | sort | uniq -c | sort -n

So, after contemplating how many time you typed the same command line, what about creating some clever shortcuts and aliases?

Some aliases and shortcuts

In my case, given the size of the current code base I'm working on finding files is something I do very often. If you're familiar with the find command you know that is a bit large command to type 50 times during the day. In my bash_aliases I have the following to find files:

  myfind() {
      find ./ -name "*$2*" -type "$1" | tee ${TMPDIR}/my_ffound | cat -n
      FFOUND_PWD=${PWD}
      FFOUND=($(cat ${TMPDIR}/my_ffound))
  }
  ff() { myfind "f" "$1"; }
  fd() { myfind "d" "$1"; }

Don't worry much about FFOUND right now I will explain it later. Now I have a neat function to find files and I added two other ones that I actually call ff (mnemonic "find files") and fd (mnemonic "find directories"). If I type ff FileName it calls myfind trying to find a file with FileName in part of the name. Just to give one example on how is the output let me find some files in my eclipse installation folder (note the number index before each file):

  eclipse$ ff junit
  1 ./plugins/org.eclipse.jdt.junit4.runtime_1.1.100.v20100526-0800.jar 
  2 ./plugins/org.junit_4.8.1.v4_8_1_v20100427-1100/junit.jar 
  3 ./plugins/org.apache.ant_1.7.1.v20100518-1145/lib/ant-junit.jar 
  4 ./plugins/org.apache.ant_1.7.1.v20100518-1145/etc/junit-noframes.xsl 
  5 ./plugins/org.apache.ant_1.7.1.v20100518-1145/etc/junit-frames-xalan1.xsl 
  6 ./plugins/org.apache.ant_1.7.1.v20100518-1145/etc/junit-frames.xsl 
  7 ./plugins/org.junit_3.8.2.v3_8_2_v20100427-1100/junit.jar 
  8 ./plugins/org.eclipse.jdt.junit.runtime_3.4.200.v20100526-0800.jar 
  9 ./plugins/org.eclipse.jdt.junit_3.6.0.v20100526-0800.jar 
  10 ./plugins/org.eclipse.jdt.junit.core_3.6.0.v20100526-0800.jar 
  11 ./plugins/org.eclipse.pde.junit.runtime_3.4.100.v20100601.jar

But after finding a file, what you do next? Copy the path and open it? List the size? Change to the directory where the file is? And after all of that, what happens with the results inside the terminal? Do you lost them and have to issue the find again? If so, what about keeping a history of this find and refer to it later? That's what the FFOUND is about. What happens is that the results are dumped into a file that's overwritten if another find is issued. So, let's see some cool things that we can do with this history.

Do you want open the file in vim the file indexed as 6? Let's make a shortcut for that:

  v() { vim $(echo ${FFOUND_PWD}/${FFOUND[$1-1]});  }

Now after the find a simple v 6 will open the file. What else can we do? Copy the file? Well, since it seems that we will refer to the list of files found a lot, so let's extract to a simpler function that returns the file path if it exists and refactor your v() function plus add another alias to change to the directory where the file is:

  fn() {
      [ ! -z ${FFOUND[$1-1]} ] && echo ${FFOUND_PWD}/${FFOUND[$1-1]};
  }
  v() { vim $(fn "$1");  }
  c() { cd $(dirname $(fn "$1")); }

And now, c 6 will go to the file directory. There's one more thing we're missing, list back our results in case we forget or lose them, so:

  lf() { echo ${FFOUND[*]} | tr -s ' ' '\n' | cat -n; }

And now you can extend this as you wish, I have for aliases for git and grep also. Moreover, there's another hidden benefit, imagine that you don't have an alias for a command (maybe because you don't use it too often), since fn function is always available inside the shell you can invoke the function for any command like:

  eclipse$ anyCommand $(fn 6)

And last but not least, here's an alias for a grep where the results are opened in vim with the quickfix window where I can navigate the results (cool if you dig around lot of files):

  vgr() { tmp=$(egrep -Rl "$1" * | xargs ) && vim -c  ":vimgrep /$1/ ${tmp} | :copen "; }

The advantage of a simple :vimgrep is that the linux grep is much faster, so I launch vim just with the list of files already found.

That's it. If you have some cool aliases please share, and thanks Aravind S.V. for some ideas and specially the help with the bash functions.

Published in Mar 28, 2011