Line-by-line Bash script execution

13 March 2009

The following script, written for Bash, executes another script in a line-by-line fashion. If a line fails, it lets you re-run that particular line.

Note: This is basically (just) a hack that runs every line in a given file with eval (see Bash man page). It won’t work for any logic that occupies multiple lines, including if and for blocks. It’s useful for some run-of-the mill household scripts like mounting and unmounting drives, backing things up, unlocking encrypted drives with cryptsetup and so forth.

You can download lbl, stick it somewhere in your path, and enjoy. (Called lbl for line-by-line execution.)

#!/usr/bin/env bash
# check for presence of parameter
if [ -z $1 ]; then
  echo "Usage: lbl filename"    
  exit 1
fi
# read the file into an array of lines
declare -a lines
let count=0
while read line; do
  lines[$count]=$line
  let count=count+1
done < "$1"
echo "=> Executing '$1' line-by-line (${#lines[@]} lines)"
# iterate lines of the file
count=0
for line in "${lines[@]}"; do
  let count=count+1
  # detect comments and output them
  if [[ ${line:0:1} == "#" ]]; then
    echo "$line"
    continue
  fi
  # preset to nonzero (error state) so that loop runs at least once
  error=-1
  # stay on this line until it executes correctly
  while [ "$error" -ne 0 ]; do
    # execute the line    
    eval "$line"
    # save the error state
    error="$?"
    # prompt to retry if error
    if [ "$error" -ne 0 ]; then
      # failed 
      echo "=> Failed on line $count: $line"
      # prompt (and get input from saved file descriptor)
      read -p "Try again (y)? " answer
      # exit program if answer is not y
      if test "$answer" != "y"; then
        exit 1;
      fi
    fi
  done
done
# successful finish
echo "=> Successfully finished executing $1"

Other tools such as the Bash Debugger are more adept at actually debugging Bash scripts. Alternately, if you simply want your script to fail when a given line fails (which is a good idea), put set -e at the top, as described in David Pashley’s blog.

Simple use case

Suppose you have a simple script like this.

#!/usr/bin/env bash
# make a directory
read -p "=> Enter a directory to create in /tmp: " name
mkdir /tmp/$name
# make a file
touch /tmp/$name/newfile
# list files in the directory
echo "=> Here is the contents of /tmp/$name"
ls -la /tmp/$name
# remove the directory 
rmdir /tmp/$name

This script just creates a directory, puts an empty file in it, then tries to remove the directory. You can download this simple script and try it yourself.

Notice that it will fail with the error “Directory not empty” because, of course, the directory contains a file.

Now try executing the script with lbl:

lbl make-a-tmp-dir

Assuming you have lbl in your path, you will see “=> Failed on line 11: rmdir /tmp/$name” followed by a prompt to see if you want to try again. You may, if you wish, open another terminal window, empty the directory, and answer “y” to the query, allowing the script to finish successfully.

In practice, this might be where, in the middle of your own script, you plug in the missing USB drive that you’re trying to mount, reconnect the network adapter that’s causing your wget to fail, or apt-get install some dependency that’s required for some command.

For fun, lbl echoes all comment lines. As a bonus, you can change the shebang) in make-a-tmp-dir to read: #!/usr/bin/env lbl

Then, automatically get lbl action by running:

./make-a-tmp-dir
antipode

,

---

Comment

  (Please preview your comment before submitting it.)
Individual article
---