argument containing nothing. Therefore, "$@" can't possible work correctly. Yet it does.
The reason is that
the shell considers the "$@" a special case. However, not all versions of the Bourne shell have the same
behavior. Some convert
"$@"
to
""
if no arguments are provided. For a general purpose wrapper program, this is wrong. The cat program would
complain that it cannot open the file provided, and would not print out the filename because there is one.
The fix for these systems is to use the pattern:
${1+"$@"}
To explain, if "$1" is defined, then replace this by "$@." If it is not defined, then do nothing. Therefore the
more portable form is:
/usr/local/$OS/$REV/$ARCH/bin/$CMD ${1+"$@"}
Status and Wasted Processes
There are two more flaws in the script: one minor and one major. The first is that the script creates a new
process unnecessarily. It's a small point, but if you want to optimize a commonly executed script, worthwhile.
The second problem is the script does not properly return the exit status. The fix is simple. Place exec before
the instruction:
exec /usr/local/$OS/$REV/$ARCH/bin/$CMD ${1+"$@"}
Normally, the shell creates a copy of itself for each line, and then executes the command on the line with the
new process. The exec command does the second step, without requiring the shell to copy itself. If the exec
command succeeds, the shell never executes the next line.
The second problem with the script is the exit status. The version without the "exec" script does not execute
the exit command. Therefore the exit status is zero. The one with the "exec" command never exits (unless the
program isn't found). Instead, the program it executes exits, and the exit status is passed to the script that
called the wrapper script. Why is this important? Well, the value of the status is the basis of flow control in
the Bourne shell.
Simple Flow Control
This is the other topic for this section. There are some subtle points, so bear with me. The simplest form is one
of these variations
command1 && command2
command1 || command2
As I have mentioned before, a program only has two ways to pass information to another process: a file/pipe,
Bourne Shell Tutorial
http://www.grymoire.com/Unix/Sh.html
38 of 66
11/21/2011 12:03 PM
or the exit status. A program cannot use environment variables to pass information back to the process that
created it. Most of the time a process does file I/O. The exit status is another quick and convenient method.
The status is an integer from 0 to 255. The shell can either examine the integer value of an exit status, or treat
the value as a boolean. Zero is true, all other values are false. If you do not provide an exit status, the system
returns with the status of the last command executed.
UNIX comes with two programs called true and false, which is simply the command "exit 0" and "exit 255."
Well, the true program doesn't even have an exit command. Since no commands are executed, the exit status
is zero. So, in essence, the true command does absolutely nothing, but takes nine lines, including the
copyright notice, to do nothing. I should warn you, however, that if you ever create a shell script that does
nothing, you risk the legal fury of the AT&T legal department for reverse engineering a program that took
untold hours to develop.
That's the scoop. Using the status gives you flow control. The "&&" is often called the "and" operator. It
executes the next command if the first command is true. The "||" operator is the "or" command, which
executes if the command is false. To illustrate:
true && echo this line IS printed
false || echo this line IS printed
true || echo this line is NOT printed
false && echo this line is NOT printed
Substitute the words "and" or "or" in the above examples, and read them quietly to yourself to understand
why. You can, if you wish, combine these operators one one line:
command && echo command succeeded || echo command failed
Allow me a brief discursion. The pipe character "|" is special. Several commands can be combined using pipes
into something called a pipeline. The shell has five different mechanisms to combine pipelines into a list. You
all know that the end of line character is one of the five. Here are examples of the other four:
cmd1 ; cmd2 ; cmd3 ; cmd4
cmd1 & cmd2 & cmd3 & cmd4
cmd1 && cmd2 && cmd3 && cmd4
cmd1 || cmd2 || cmd3 || cmd4
The semicolon tells the shell to operate sequentially. First "cmd1" is executed, then "cmd2," etc. Each
command starts up, and runs as long as they don't need input from the previous command. The "&" command
launches each process in a detached manner. The order is not sequential, and you should not assume that one
command finishes before the other. The last two examples, like the first, execute sequentially, as long as the
status is correct. In the "&&" example, "cmd4" is executed if all three earlier commands pass. In the "||"
example, "cmd4" is executed if the first three fail. The "&&" and "||" have higher precedence than ";" and
"&," but lower than "|." Therefore
a | b && c ; d || e | f ;
is evaluated as
(a | (b && c)) ; ((d || e) | f) ;
The || and && commands can be used for a simple if-then-else. Note that
cmd1 && cmd2 || cmd3
Bourne Shell Tutorial
http://www.grymoire.com/Unix/Sh.html
39 of 66
11/21/2011 12:03 PM
if cmd1 succeeds, and cmd2 fails, then cmd3 will be executed. To prevent this, one can use
cmd1 && {cmd2;exit 0; } || cmd3
Changing Precedence
If you want to change the precedence, or order of evaluation, you can use either curly braces or
parenthesis. There are some subtle differences between these two. Syntactically, there are two
differences:
(cmd1; cmd2) | cmd3
{ cmd1; cmd2; } | cmd3
Notice the semicolon at the end of the list in the curly brace. Also notice a space is required after the
first "{." There is another difference: the parenthesis causes the shell to execute a new process, while
the curly brace does not. You can set variables in a curly brace, and it will be known outside the braces.
In the example below, the first echo prints "OLD" and the second prints "NEW:"
a=OLD
(a=NEW) ; echo $a
{ a=NEW;} ; echo $a
Putting it all together
I've explained the pieces. Now I'll try to put everything together. If you want to temporarily ignore a
series of commands, without adding a "#" before each line, use the following:
false && {
command1
command2
command3
}
Change the false to true, and the commands get executed. Braces and parenthesis can be nested:
A && {
B && {
echo "A and B both passed"
} || {
echo "A passed, B failed"
}
} || echo "A failed"
The parenthesis and curly brace are useful when you want to merge standard output of multiple
commands. For instance, suppose you want to add a line containing "BEGIN" to the middle of a
pipeline, before the "C" command:
A | B | C | D
Bourne Shell Tutorial
http://www.grymoire.com/Unix/Sh.html
40 of 66
11/21/2011 12:03 PM