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?

Asked By: AnthonyBB

||

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
Answered By: Kusalananda
Categories: Answers Tags: , ,
Answers are sorted by their score. The answer accepted by the question owner as the best is marked with
at the top-right corner.