Bash redirection weird behavior
I was messing around with trapping exit codes and redirecting stdout
and stderr
of a case statement when I ran into an interesting outcome that I am hoping someone can shine some light into.
I am using bash 5.1.16.
This block can be used to replicate the behavior I am seeing:
#!/bin/sh -x
cleanup () {
case $? in
1) echo "message 1" ;;
9) echo "message 2" ;;
esac
}
trap cleanup EXIT
case 0 in
0) exit 1 ;;
2) echo "yay" ;;
esac > /dev/null 2>&1
The xtrace output with > /dev/null 2>&1
:
+ trap cleanup EXIT
The xtrace output without > /dev/null 2>&1
:
+ trap cleanup EXIT
+ case 0 in
+ exit 1
+ cleanup
+ case $? in
+ echo 'message 1'
message 1
What’s going on here? How is the redirection causing that the case statement isn’t executed at all?
The EXIT
trap is executed with the redirections that are in place when the trap is called. In your code, calling exit 1
in the main case
statement causes the cleanup
function to inherit the redirections from that statement.
A shorter example which does not print anything to standard output:
cleanup () {
echo bye
}
trap cleanup EXIT
exit >/dev/null
Remove >/dev/null
to have the script output bye
.
The tracing output produced by set -x
is written to the standard error stream and your code is redirecting this stream to /dev/null
too. In short, your function’s case
statement is executed, but you discard all output from the script so you won’t see the output from its echo
, nor the tracing output.
Also note that some other shells, like dash
and ksh
, will not behave in this way.
To work around this in the bash
shell, you may duplicate the standard error file descriptor at the start of the script and then explicitly use that in your cleanup
function. I’m using the standard error stream for this as I’m presuming it will be used for diagnostic messages.
Your code with my additions:
#!/bin/bash -x
exec {fd}>&2
cleanup () {
case $? in
1) echo "message 1" ;;
9) echo "message 2" ;;
esac >&$fd
}
trap cleanup EXIT
case 0 in
0) exit 1 ;;
2) echo "yay" ;;
esac > /dev/null 2>&1
The descriptor allocated by the shell and assigned to fd
will be 10 or higher.
Since sh
is not always bash
, I have also changed the #!
-line to explicitly call the bash
executable.
Running this:
$ ./script
+ exec
+ trap cleanup EXIT
message 1