4

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?

1
  • how do you run the script if you can see the xtrace output even with stderr redirected to /dev/null?
    – ilkkachu
    Jun 3, 2022 at 23:09

1 Answer 1

5

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
2
  • That's really interesting actually that the redirection is inherited, but to some extent makes sense why it would, just defeats the purpose of what I'm trying to achieve where the case statement produces no output and any exits are dealt with by the trap with a corresponding message to explain why the early exit. Thank you!
    – AnthonyBB
    Jun 3, 2022 at 17:13
  • 1
    @AnthonyBB See workaround at end.
    – Kusalananda
    Jun 3, 2022 at 17:31

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .