6

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!

Jolta
  • 2,620
  • 1
  • 29
  • 42
cristiprg
  • 103
  • 1
  • 9
  • 1
    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. – Etan Reisner Dec 18 '14 at 12:41
  • 1
    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. – cristiprg Dec 18 '14 at 12:51
  • `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. – Etan Reisner Dec 18 '14 at 12:53
  • 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? – cristiprg Dec 18 '14 at 13:05
  • 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. – Etan Reisner Dec 18 '14 at 13:14

1 Answers1

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.

(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.

Etan Reisner
  • 77,877
  • 8
  • 106
  • 148