Thursday 1 May 2014

Shell script one-liner of the day

It continues to amaze me how powerful one line of shell script can be.

Here's one I used this morning that'll checkout from git the directories containing a certain keyword at a particular revision in the past:

rev=abc12345; git ls-tree $rev | cut -f2 | grep -i keyword | tr '\n' '\0' | xargs -0 git checkout $rev --

Yes, it's ugly, although I imagine that better script programmers than me could clean it up significantly (writing this I already figured out how to trim the tr command from the pipeline). Yes, it relies on several slightly odd text processing tricks and implicit knowledge, each carried out by a different executable. But it's undeniable how quick it is to do fairly complex things in shell, which an exclusive GUI user could only dream of.

What makes this possible is just a few shared ideas:
  • Processes have an input and output stream and the shell can pipe between processes
  • Sequences of items are represented by text delimited by null or new line characters
  • Fields of an item are represented by text delimited by tabs
Then we have our few simple tools:
  • git ls-tree outputs machine readable text
  • cut selects a tab-delimited field from an item, where each item is on a new line
  • grep filters items based on a regular expression
  • tr transforms each item and outputs it again
  • xargs runs a command once for each item in its input
  • git checkout does its job with parameters from the command line
Writing the above I think I've understood a little better Doug McIlroy's famous quote:
This is the Unix philosophy: Write programs that do one thing and do it well. Write programs to work together. Write programs to handle text streams, because that is a universal interface.