David A. Harding
Thursday, 22 Mar 2007
I try to write shell scripts that do one thing and do it tolerably.
Often that means I write more than one script for any non-trivial
project. For example, I have about half-a-dozen scripts that help me
maintain my website. I like sharing
variables—mostly file and path names—between related scripts so I
only need to define a variable once and only need to redefine it once if
something changes. In bash or any POSIX shell, this is
easy: add the variable definitions to a file and use the shell's builtin
source function. For example, I put the following lines in
the file my.vars in my home directory:
HI="Hello," YOU=`whoami`
The following script sources the variables and uses them:
#!/bin/bash source $HOME/my.vars echo "$HI $YOU!"
The output depends on my username. On my desktop, my username is harda.
Hello, harda!
The POSIX Shell
The
POSIX specification,
a set of rules for how Unix like operating systems should work named by
Richard Stallman,
doesn't define source as a shell builtin even though shells
like bash and zsh support it. For
interoperability, replace source with a period.
. $HOME/my.vars
By default, if I don't provide a complete path to the file to be
sourced, the shell will look for the file in the directories listed
in the $PATH environmental variable. bash
will also search the current working directory—this lets me
use a relative path. I always use complete path so I can run my
scripts from any directory and without worrying about
$PATH.
Checking for Errors
If I expect a variable named $TMPDIR and run the
command, rm -rf $TMPDIR, but the variable hasn't been
defined, I'll delete everything in the current directory. This
can happen if a file doesn't get sourced.
The simplest way to prevent something like this from happening is to
change the beginning of my script from #!/bin/bash to
#!/bin/bash -u Now if I use a variable that hasn't
been defined, bash will exit with an error before
running a command that uses an undefined variable. For example, if I add
-u to the beginning of the program above, delete the
file my.vars, and re-run the program, the output
changes:
./foo.sh: line 3: /home/harda/my.vars: No such file or directory ./foo.sh: line 5: HI: unbound variable
The good news is bash told me there was a problem. The bad
news is there are two error messages but only one cause: the file I sourced
is missing. A little more work will make things clearer. Leaving the
-u switch in, I can check for errors in the source
statement:
. $HOME/my.vars || { echo "Can't source my.vars!" echo "I'm exiting with an error code now. Sorry." exit 1 }
The above code says, source my.vars
(. $HOME/my.vars) and if it doesn't work
(||) run all of the code between the curly braces
({}). exit means stop running the program;
its single optional argument sets the return code. 0, the value if I
don't specify a code, means the program exited true (i.e.
successfully); any number between 1 and 255 means an error or failure
occurred. Using the code above, and with my.vars still
deleted, the error output of the program becomes clearer:
./foo.sh: line 3: /home/harda/my.vars: No such file or directory
Can't source my.vars!
I'm exiting with an error code now. Sorry.
Final Code
#!/bin/bash -u . $HOME/my.vars || { echo "Can't source my.vars!" echo "I'm exiting with an error code now. Sorry." exit 1 } echo "$HI $YOU!"