Bash: Trap ERR does not work when pipe operator is used
Asked Answered
R

1

7

I am trying to log everything that comes out of stdout and stderr into a log file and still preserve the console. For this, I just appended: |& tee -a log_file.log to every command.
However, I also want to run a custom command if any error occurred during the script. For this, I added the following at the beginning of the script: trap "echo Non-zero exit code detected" ERR.
The problem is by using the pipe operator, the echo in the trap does not execute anymore.

Script 1, without pipe:

$cat test.sh
#!/bin/bash

trap "echo Non-zero exit code detected!" ERR

function fail_please()
{
    echo "Returning non-zero exit code!"
    return 1
}

fail_please 

Output 1:

$ ./test.sh 
Returning non-zero exit code!
Non-zero exit code detected!

Script 2, with pipe:

$ cat test.sh
#!/bin/bash

trap "echo Non-zero exit code detected!" ERR

function fail_please()
{
    echo "Returning non-zero exit code!"
    return 1
}

fail_please |& tee log_file.log 

Output 2:

$ ./test.sh
Returning non-zero exit code!
$ cat log_file.log 
Returning non-zero exit code!

In output 2, the message "Non-zero exit code detected!" is missing. Any idea why? Thanks!

Reactance answered 18/12, 2014 at 12:39 Comment(5)
The ERR trap fires for "simple commands" a pipeline is not a simple command. It might fire for the result of the whole pipeline (I'm not sure) and you might be able to get something closer to what you want by setting pipefail. This is one of the reasons people often don't recommend using set -e as it has surprising details like this.Mackey
Thank you! I added set -o pipefail and it worked. However, I didn't quite understand why set -e isn't recommended. Does it have other caveats? Also, please add an answer to accept it.Reactance
set -e has similar caveats to your ERR trap. It doesn't fire in all the situations you might expect it to. And some of those situations are external to your otherwise working code.Mackey
Just to clarify, in script 2, by setting pipefail, the return code of the pipe is still 0 (because tee returns 0), but it enables my ERR trap to fire in case the first command failed. Is this correct?Reactance
I just added a comment about why pipefail works to my answer but yes, it sets the return to the exit status of the last command that fails instead of always being the exit status of the last command in the pipeline.Mackey
M
6

The ERR trap fires for "simple commands" a pipeline is not a simple command.

It might fire for the result of the whole pipeline (I'm not sure) and you might be able to get something closer to what you want by setting pipefail.

(Note: This is one of the reasons people often don't recommend using set -e as it has surprising details like this.)

The reason pipefail works is that normally the return status of a pipeline is the return of the last command but with pipefail on it becomes the return status of the last command that fails.

The return status of a pipeline is the exit status of the last command, unless the pipefail option is enabled. If pipefail is enabled, the pipeline’s return status is the value of the last (rightmost) command to exit with a non-zero status, or zero if all commands exit success- fully.

Mackey answered 18/12, 2014 at 13:1 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.