1

Script

#!/usr/bin/env bash

# Exit on error. Append "|| true" if you expect an error.
set -o errexit
# Exit on error inside any functions or subshells.
set -o errtrace
# Do not allow use of undefined vars. Use ${VAR:-} to use an undefined VAR
set -o nounset
# Catch the error in case mysqldump fails (but gzip succeeds) in `mysqldump |gzip`
set -o pipefail
# Turn on traces, useful while debugging but commented out by default
 set -o xtrace

bash_backtrace() {
    echo TEST
    ls -l /proc/$$/fd >&2
}

trap bash_backtrace ERR

CMD="ls /does-not-exist"
eval "${CMD}" > /tmp/foo
exit

Output

$ ./test.sh 
+ trap bash_backtrace ERR
+ CMD='ls /does-not-exist'
+ eval 'ls /does-not-exist'
++ ls /does-not-exist
ls: cannot access /does-not-exist: No such file or directory
+++ bash_backtrace
+++ echo TEST
+++ ls -l /proc/19650/fd
total 0
lrwx------. 1 sbarre sbarre 64 Apr 18 15:57 0 -> /dev/pts/0
l-wx------. 1 sbarre sbarre 64 Apr 18 15:57 1 -> /tmp/foo
lrwx------. 1 sbarre sbarre 64 Apr 18 15:57 10 -> /dev/pts/0
lrwx------. 1 sbarre sbarre 64 Apr 18 15:57 2 -> /dev/pts/0
lr-x------. 1 sbarre sbarre 64 Apr 18 15:57 255 -> /home/sbarre/test.sh

Because my eval is throwing an error and being caught by the trap, stdout is still pointing to /tmp/foo. So any echo's in my trap function will go to that file, instead of to the terminal.

How can I reset this safely in the trap function? I need to handle when the script itself is run in a way where its stdout is being redirected.

$ ./test.sh > log.txt

I'd want to "fix" stdout back to log.txt from /tmp/foo

Jeff Schaller
  • 67,283
  • 35
  • 116
  • 255

2 Answers2

2

This is exactly what the standard error stream is for.

bash_backtrace() {
    echo TEST >&2
    ls -l "/proc/$$/fd" >&2
}

The trap is outputting a diagnostic message (TEST). This shouldn't go to standard output but to standard error.

Related: "Do progress reports/logging information belong on stderr or stdout?"

Kusalananda
  • 333,661
  • 1
    If I was also capturing stderr from the eval, I would have the same problem though. – Slashterix Apr 19 '17 at 15:48
  • 1
    @Slashterix You would have the same issue with every single Unix command line utility, tool and application. You could write directly to the TTY (echo TEST >$(tty)) but there is nothing guaranteeing that there actually is a TTY there for you to write to, and it would make it impossible (or very hard) to redirect it to a file. It would also break deep-rooted conventions on how Unix utilities should function. – Kusalananda Apr 19 '17 at 15:49
0

I've solved this by using exec to clone the handles and then restore them in the trap function. This way no matter where STDERR is going when the script starts, output from the trap will go there too.

Script

#!/usr/bin/env bash

# Exit on error. Append "|| true" if you expect an error.
set -o errexit
# Exit on error inside any functions or subshells.
set -o errtrace
# Do not allow use of undefined vars. Use ${VAR:-} to use an undefined VAR
set -o nounset
# Catch the error in case mysqldump fails (but gzip succeeds) in `mysqldump |gzip`
set -o pipefail
# Turn on traces, useful while debugging but commented out by default
set -o xtrace

# Copy STDOUT and STDERR
exec 3>&1 4>&2

bash_backtrace() {
    ls -l /proc/$$/fd >$(tty)
    # Restore STDOUT and STDERR
    exec 1>&3 2>&4
    echo TEST
    echo >&2 ERROR
    ls -l /proc/$$/fd >&2
}

trap bash_backtrace ERR

CMD="ls /does-not-exist"
eval "${CMD}" > /tmp/foo 2> /tmp/bla
exit

Output

+ exec
+ trap bash_backtrace ERR
+ CMD='ls /does-not-exist'
+ eval 'ls /does-not-exist'
total 0
lrwx------. 1 sbarre sbarre 64 Apr 19 13:22 0 -> /dev/pts/0
l-wx------. 1 sbarre sbarre 64 Apr 19 13:22 1 -> /tmp/foo
lrwx------. 1 sbarre sbarre 64 Apr 19 13:22 10 -> /dev/pts/0
lrwx------. 1 sbarre sbarre 64 Apr 19 13:22 11 -> /dev/pts/0
l-wx------. 1 sbarre sbarre 64 Apr 19 13:22 2 -> /tmp/bla
lr-x------. 1 sbarre sbarre 64 Apr 19 13:22 255 -> /home/sbarre/test.sh
lrwx------. 1 sbarre sbarre 64 Apr 19 13:22 3 -> /dev/pts/0
lrwx------. 1 sbarre sbarre 64 Apr 19 13:22 4 -> /dev/pts/0
+++ echo TEST
TEST
+++ echo ERROR
ERROR
+++ ls -l /proc/11910/fd
total 0
lrwx------. 1 sbarre sbarre 64 Apr 19 13:22 0 -> /dev/pts/0
lrwx------. 1 sbarre sbarre 64 Apr 19 13:22 1 -> /dev/pts/0
lrwx------. 1 sbarre sbarre 64 Apr 19 13:22 10 -> /dev/pts/0
lrwx------. 1 sbarre sbarre 64 Apr 19 13:22 11 -> /dev/pts/0
lrwx------. 1 sbarre sbarre 64 Apr 19 13:22 2 -> /dev/pts/0
lr-x------. 1 sbarre sbarre 64 Apr 19 13:22 255 -> /home/sbarre/test.sh
lrwx------. 1 sbarre sbarre 64 Apr 19 13:22 3 -> /dev/pts/0
lrwx------. 1 sbarre sbarre 64 Apr 19 13:22 4 -> /dev/pts/0