Saturday, January 06, 2007

The Unix Shell: Argh

Suppose you're trying to write a simple shell script. And suppose you very badly want to put a command into a shell variable before you execute it. And suppose that command very badly needs to accept double-quoted strings as arguments. Oh, the layers of indirection! How will the shell tokenize such a thing?

Let the command in question be the following homely shell script.

string-arg.sh:
#! /bin/sh
# Usage: string-arg.sh [-s STRING]*
while test ${1+set}; do
case $1 in
"-s")
if ! test ${2+set}; then
echo "no string after -s"
exit 1
else
echo "string arg=\"$2\""
shift 2
continue
fi
esac
echo "bad arg: $1"
exit 1
done

Let's see what happens.
% ./string-arg.sh -s "quoted arg"
string arg="quoted arg"
% cmd="./string-arg.sh -s \"quoted arg\""
% echo $cmd
./string-arg.sh -s "quoted arg"
% $cmd
string arg=""quoted"
bad arg: arg"
% eval $cmd
string arg="quoted arg"


AND REMEMBER, KIDS: echo `foo` gives you the output of foo and { foo; echo $? } gives you its exit value.
if foo 
then bar
fi
executes bar if foo succeeds (conventionally), which is equivalent to
foo && bar
in the short-circuiting parlance.
if ! foo 
then bar
fi
executes bar if foo fails (conventionally), which is equivalent to
foo || bar
in the short-circuiting parlance.
if `foo`
then bar
fi
executes bar if the command output by foo exists and returns exit value 0 (if it doesn't exist, it will just exit).
if test `foo`
then bar
fi
executes bar if foo doesn't produce any output.

[UPDATE] One more thing. Setting shell variables.
% CMD=cmd arg
bash: arg: command not found
% CMD= cmd arg
bash: cmd: command not found
% CMD= "cmd arg"
bash: cmd arg: command not found
% CMD="cmd arg"
% echo $CMD
cmd arg

This often trips me up, because make is much more forgiving and I hack more Makefiles than shell scripts.

[UPDATE 1/8/2007] I got the if-then stuff wrong the first time. Which goes to show you how desperately I need this tutorial. In shell-world exit code 0 is "true" and exit code not-0 is "false". This is sort of the opposite of the C convention, with the caveat that exit values and arithmetic values shouldn't be conflated.

Another trip-up from C to the shell is that whitespace makes a bigger difference. if ! foo; then bar; fi is not the same as if !foo; then bar; fi.
if foo
then bar
fi
is not the same as if foo then bar fi. {foo; bar;} is not the same as { foo; bar;}. And so on, world without end.

No comments: