Shell hacks



Redirecting I/O Script-wide

The exec command can be used to redirect I/O script-wide. If exec is not given a command to execute, it applies whatever I/O redirections are specified to the current shell itself.

For example, instead of appending >/tmp/logfile to capture the output of every command to a file, use this to redirect stdout:

exec >/tmp/logfile

To direct stderr to stdout, use:

exec 2>&1

Or the reverse, stdout to stderr:

exec 1>&2

Logging and Monitoring with Tee

Sometimes you want to capture all standard output to a log file while monitoring the output yourself. We can use the exec I/O redirection to do this also along with process substitution:

exec > >(tee -a ${0##*/}.log)

If you wanted to redirect stderr to a different file:

exec 2> >(tee -a ${0##*/}.err)

Printing and Logging to Syslog

Let’s make use of the exec to log the output of the script to syslog instead of a file. We have the logger command, which will take a message either on the command line or from standard input and write it to syslog. The -s option instructs it to also write the log messages to standard error. And if we did not know about process substitution, we could use a named FIFO to have logger read from and standard output and error to write to.


if [[ -z "$FIFO" ]]; then
    export FIFO="/tmp/${prog}.$RANDOM"

if [[ ! -e "$FIFO" ]]; then
    mkfifo -m 666 "$FIFO"
    trap "rm $FIFO" ERR EXIT
    logger -t "${prog}:" -i -s <"$FIFO" &

exec >"$FIFO" 2>&1

Trace-visible Comments

Sometimes when debugging shell scripts, it’s nice to be able to tell where you are. It’s not always clear, even when running in trace-mode (set -x). Usually this is accomplished by inserting echo statements that tell you where you are. When you’re finished debugging, you have to remove or comment-out these (and how many times have you discovered one you forgot to remove?) Instead, you can use the colon-builtin to provide “traceable” comments. The colon command is one of those little-used commands that does nothing other than provide a true value, so it’s often used in while loops that you expect to exit in ways other than the conditional or to ensure that some particular line always returns true (frequently seen in RPM spec files, for example). The colon command ignores any parameters given to it, so you can add a comment after the colon and it will be visible when run with tracing turned on and invisible otherwise. Note that it is actually a command and not a comment so it cannot be used exactly as a comment would, such as at the end of a statement (although you can use the semi-colon statement separator, as you’d expect with any other). true and false also ignore any supplied parameters, but it seems less obvious than not.

Here’s an example script:

: This is an invisible traced comment
set -x
# This is an untraced comment
: This is a traced comment

Which produces the following output:

$ ./
+ : This is a traced comment

Redirecting To stderr

Under bash, /dev/stderr is an internally-recognized device that, when redirected to, writes the output to stderr. Under Linux and Solaris, /dev/stderr exists as a character device for the current process’s stderr. (It’s actually a symlink to a character device, but the effect is the same.) So writing to /dev/stderr in a shell that didn’t provide an internal /dev/stderr should work just fine. But that’s not true with ksh on AIX. With ksh, one might be inclined to use “print -u 2”, but that doesn’t work in bash.

If you have to worry about portability, use the obscure redirection to redirect stdout to stderr:

$ echo "this writes to stderr" 1>&2

Test it:

$ echo "this writes to stderr" 1>&2 |cat >/dev/null
this writes to stderr

(Usually one redirects stderr to stdout using “2>&1”–this is just the reverse.)