Classic Shell Scripting - Arnold Robbins [89]
do Loop over them
diff -c /old/code/dir/$i $i | more Compare old version to new in pager program
done
The shell first executes cd /old/code/dir ; echo *.c. The resulting output (a list of files) then becomes the list to use in the for loop.
The backquoted form is the historical method for command substitution, and is supported by POSIX because so many shell scripts exist that use it. However, all but the most simplest uses become complicated quickly. In particular, embedded command substitutions and/or the use of double quotes require careful escaping with the backslash character:
$ echo outer `echo inner1 \`echo inner2\` inner1` outer
outer inner1 inner2 inner1 outer
This example is contrived, but it illustrates how backquotes must be used. The commands are executed in this order:
echo inner2 is executed. Its output (the word inner2) in placed into the next command to be executed.
echo inner1 inner2 inner1 is executed. Its output (the words inner1 inner2 inner3) is placed into the next command to be executed.
Finally, echo outer inner1 inner2 inner1 outer is executed.
Things get worse with double-quoted strings:
$ echo "outer +`echo inner -\`echo \"nested quote\" here\`- inner`+ outer"
outer +inner -nested quote here- inner+ outer
For added clarity, the minus signs enclose the inner command substitution, and plus signs enclose the outer one. In short, it can get pretty messy.
Because nested command substitutions, with or without quoting, quickly become difficult to read, the POSIX shell adopted a feature from the Korn shell. Instead of using backquotes, enclose the command in $(...). Because this construct uses distinct opening and closing delimiters, it is much easier to follow. Compare the earlier examples, redone with the new syntax:
$ echo outer $(echo inner1 $(echo inner2) inner1) outer
outer inner1 inner2 inner1 outer
$ echo "outer +$(echo inner -$(echo "nested quote" here)- inner)+ outer"
outer +inner -nested quote here- inner+ outer
This is much easier to read. Note also how the embedded double quotes no longer need escaping. This style is recommended for all new development, and it is what we use in many of the examples in this book.
Here is the for loop we presented earlier that compared different versions of files from two different directories, redone with the new syntax:
for i in $(cd /old/code/dir ; echo *.c) Generate list of files in /old/code/dir
do Loop over them
diff -c /old/code/dir/$i $i Compare old version to new
done | more Run all results through pager program
The differences here are that the example uses $(...) command substitution, and that the output of the entire loop is piped into the more screen-pager program.
Using sed for the head Command
Earlier, Example 3-1 in Chapter 3 showed a simple version of the head command that used sed to print the first n lines of a file. The real head command allows you to specify with an option how many lines to show; e.g., head -n 10 /etc/passwd. Traditional pre-POSIX versions of head allowed you to specify the number of lines as an option (e.g., head -10 /etc/passwd), and many longtime Unix users are used to running head that way.
Using command substitution and sed, we can provide a slightly modified shell script that works the same way as the original version of head. It is shown in Example 7-2.
Example 7-2. The head command as a script using sed, revised version
# head --- print first n lines
#
# usage: head -N file
count=$(echo $1 | sed 's/^-//') # strip leading minus
shift # move $1 out of the way
sed ${count}q "$@"
When this script is invoked as head -10 foo.xml, sed ends up being invoked as sed 10q foo.xml.
Creating a Mailing List
Consider the following problem. New versions of the various Unix shells appear from time to time, and at many sites users are permitted to choose their login shell from among the authorized ones listed in /etc/shells. Thus, it would be nice for system management to notify users by email