Bourne Shell Scripting/Appendix D: Cookbook

Branch on extensions
When writing a bash script which should do different things based on the extension of a file, the following pattern is helpful.

(Source: slike.com Bash FAQ).

Rename several files
This recipe shows how to rename several files following a pattern.

In this example, the user has huge collection of screenshots. This user wants to rename the files using a Bourne-compatible shell. Here is an "ls" at the shell prompt to show you the filenames. The goal is to rename images like "snapshot1.png" to.

First, to add a "0" (zero) before snapshots 1 through 9, write a for loop (in effect, a short shell script).
 * Use ? which is a filename pattern for a single character. Using it, I can match snapshots 1 through 9 but miss 10 through 83 by saying snapshot?.png.
 * Use ${parameter#pattern} to substitute the value of parameter with the pattern removed from the beginning. This is to get rid of "snapshot" so I can put in "snapshot0".
 * Before actually running the loop, insert an "echo" to test that the commands will be correct.

That seems good, so run it by removing the "echo".

An ls confirms that this was effective.

Now change prefix "snapshot" to "nethack-kernigh-22oct2005-". Run a loop similar to the previous one:

This saves the user from typing 83 "mv" commands.

Long command line options
The builtin getopts does not support long options so the external getopt is required. (On some systems, getopt also does not support long options, so the next example will not work.)

The call to <tt>getopt</tt> quotes and reorders the command line arguments found in <tt>$@</tt>. <tt>set</tt> then makes replaces <tt>$@</tt> with the output from <tt>getopt</tt>

Another example of getopt use can also be found in the Advanced Bash Script Guide

Process certain files through xargs
In this recipe, we want to process a large list of files, but we must run one command for each file. In this example, we want to convert the sampling rates of some sound files to 44100 hertz. The command is <tt>sox file.ogg -r 44100 conv/file.ogg</tt>, which converts <tt>file.ogg</tt> to a new file <tt>conv/file.ogg</tt>. We also want to skip files that are already 44100 hertz.

First, we need the sampling rates of our files. One way is to use the <tt>file</tt> command:

(The files in this example are from Secret Maryo Chronicles.) We can use <tt>grep -v</tt> to filter out all lines that contain '44100 Hz':

We finished with "grep" and "file", so now we want to remove the other info and leave only the filenames to pass to "sox". We use the text utility <tt>cut</tt>. The option <tt>-d:</tt> divides each line into fields at the colon; <tt>-f1</tt> selects the first field.

We can use another pipe to supply the filenames on the standard input, but "sox" expects them as arguments. We use <tt>xargs</tt>, which will run a command repeatedly using arguments from the standard input. The <tt>-n1</tt> option specifies one argument per command. For example, we can run <tt>echo sox</tt> repeatedly:

However, these commands are wrong. The full command for cannon_1.ogg, for example, is <tt>sox cannon_1.ogg -r 44100 conv/cannon_1.ogg</tt>. "xargs" will insert incoming data into placeholders indicated by "{}". We use this strategy in our pipeline. If we have doubt, then first we can build a test pipeline with "echo":

It worked, so let us remove the "echo" and run the "sox" commands:

After a wait, the converted files appear in the <tt>conv</tt> subdirectory. The above three lines alone did the entire conversion.

Simple playlist frontend for GStreamer
If you have GStreamer, the command <tt>gst-launch filesrc location=filename ! decodebin ! audioconvert ! esdsink</tt> will play a sound or music file of any format for which you have a GStreamer plugin. This script will play through a list of files, optionally looping through them. (Replace "esdsink" with your favorite sink.)

This script demonstrates some common Bourne shell tactics:
 * "loop" is a boolean variable. It works because its values "true" and "false" are both Unix commands (and sometimes shell builtins), thus you can use them as conditions in <tt>if</tt> and <tt>while</tt> statements.
 * The shell builtin "shift" removes $1 from the argument list, thus shifting $2 to $1, $3 to $2, and so forward. This script uses it to process an "-l" option.
 * The substitution <tt>${0##*/}</tt> gives everything in $0 after the last slash, thus "playlist", not "/home/musicfan/bin/playlist".