Friday, June 04, 2010

Stupid Bash Tricks

I grew up scripting with the C shell (csh), and I'm still more comfortable with it than any other shells. However, for really complicated scripts I find that BASH (the Bourne-again shell) is more flexible. The problem is that I don't really know how to use it all that well.

I could buy a book, I suppose, but that costs money. And anyway, much of what I want is already on the web. For example, we have

I've used all of these, but the only way to really remember something is to build an example, and refer back to it as needed. So here, presented for your consideration, are a couple of examples.

The first one just counts. It's basically here to show how to do math with variables in BASH:

#! /bin/bash

typeset -i count

echo $count

while (( $count < $2 ))
 echo $count

Actually, that one is adapted from the Korn shell (ksh), but it works in bash. A purer bash form would be

#! /bin/bash

let count=$1
echo $count

while (( $count < $2 ))
 let count=$count+1
 echo $count

Either way, if we call the file count, then it counts from the first argument up to the second:

./count 5 10

On to another program. I often have programs that recompute a certain quantity repeatedly, say the total energy in a Density Functional Theory run. For example,

                          TOTAL ENERGY=      -551.5802151120
                          TOTAL ENERGY=      -551.5798689965
                          TOTAL ENERGY=      -551.5810124810
                          TOTAL ENERGY=      -551.5809217653
                          TOTAL ENERGY=      -551.5796453493
                          TOTAL ENERGY=      -551.5797608209
                          TOTAL ENERGY=      -551.5806860076
                          TOTAL ENERGY=      -551.5804690663
                          TOTAL ENERGY=      -551.5793575483
                          TOTAL ENERGY=      -551.5794583810

where I only want the last line. For a single file, I could get that with

$ grep "TOTAL ENERGY=" INFO | tail -1
                          TOTAL ENERGY=      -551.5794583810

but I want to look at multiple files, make a note of the file I'm looking at, and extract the last entry. So I wrote this little script, which I call lastgrep. Among other things, it shows you how to use the shift command to delete elements from the calling string.

#! /bin/bash

# usage

# lastgrep string  file1 file2 file3 file4

# finds the last occurrence of string in each of the files
#  listed in the argument list.  If string contains white space
#  it should be quoted


# Pop the first argument (string) off the stack.  The
#  remaining arguments should be files


for file in $@
    echo -n $file

#    Put quotes around $string to keep white space in place

    grep "$string" $file | tail -1

So if I have a set of files INFO in directories a100, a200, a300, I might do something like this:

$ lastgrep "TOTAL ENERGY=" a*/INFO
a100/INFO  TOTAL ENERGY=      -551.7810244761
a200/INFO  TOTAL ENERGY=      -551.6774784178
a300/INFO  TOTAL ENERGY=      -551.6192819148

What this doesn't have is a way of passing arguments to grep. That's for another time.