13

I'm trying to execute following code:

set -euxo pipefail
yes phrase | make installer

Where Makefile uses phrase from stdin to create installer file. However, this commands ends in error code 141, which breaks my CI build. This example can be simplified to:

yes | tee >(echo yo)

From what is see here: Pipe Fail (141) when piping output into tee -- why? - this error means that pipe consumer just stopped consuming output - which is perfectly fine in my case.

Is there a way to suppress pipe error, and just get the return code from make installer?

4 Answers4

14

A 141 exit code indicates that the process failed with SIGPIPE; this happens to yes when the pipe closes. To mask this for your CI, you need to mask the error using something like

(yes phrase ||:) | make installer

This will run yes phrase, and if it fails, run : which exits with code 0. This is safe enough since yes doesn’t have much cause to fail apart from being unable to write.

To debug pipe issues such as these, the best approach is to look at PIPESTATUS:

yes phrase | make installer || echo "${PIPESTATUS[@]}"

This will show the exit codes for all parts of the pipe on failure. Those which fail with exit code 141 can then be handled appropriately. The generic handling pattern for a specific error code is

(command; ec=$?; if [ "$ec" -eq 141 ]; then exit 0; else exit "$ec"; fi)

(thanks Hauke Laging); this runs command, and exits with code 0 if command succeeds or if it exits with code 141. Other exit codes are reflected as-is.

Stephen Kitt
  • 434,908
3

Is there a way to suppress pipe error, and just get the return code from make installer?

An alternative solution to the other excellent answers already posted, if you don't want to run your whole command pipe in a subshell (e.g. you want to be able to set variables):

yes phrase | make installer || { ec=$?; [ $ec -eq 141 ] && true || (exit $ec); }

generic syntax:

cmd1 | cmd2 | cmd3 || { ec=$?; [ $ec -eq 141 ] && true || (exit $ec); }

This uses the exit command in a subshell to preserve the original exit code from the command pipe intact, if it's not 141. Thus, it will have the intended effect if set -e (set -o errexit) is in effect along with set -o pipefail.

We can use a function for cleaner code, which allows the use of return instead of the exit in a subshell trick:

handle_pipefails() { 
    # ignore exit code 141 from simple command pipes
    # - use with: cmd1 | cmd2 || handle_pipefails $?
    (( $1 == 141 )) && return 0
    return $1
}

then use it or test it as:

yes | head -n 1 || handle_pipefails $? echo "ec=$?"

then change the tested code from 141 to e.g. 999 in

the function, and see that ec was in fact captured as

141

An alternative, if you wanted to test the exit code of other commands involved in a more complex pipe, could be to test the whole PIPESTATUS:

handle_pipefails2() {
    # ignore exit code 141 from more complex command pipes
    # - use with: cmd1 | cmd2 | cmd3 || handle_pipefails2 "${PIPESTATUS[@]}"
    for x in "$@"; do
        (( $x == 141 )) || { (( $x > 0 )) && return $x; }
    done
    return 0
}
2
( yes phrase ; exit 0 ) | make installer
Hauke Laging
  • 90,279
  • 1
    does not make a difference, (yes ; exit 0) | tee >(echo yo) still returns 141 – carbolymer Apr 27 '20 at 13:43
  • @carbolymer That's not what happens on my system. Make sure the exit code does not come from make: ( yes phrase ; exit 0 ) | ( make installer ; echo "make exit code: ${?}") – Hauke Laging Apr 27 '20 at 14:31
0

After some digging, I've found this answer: https://stackoverflow.com/questions/22464786/ignoring-bash-pipefail-for-error-code-141#comment60412687_33026977 Basically, using:

set -euxo pipefail
yes phrase | make installer || (ec=$? ; if [ "$ec" -eq 141 ]; then exit 0; else exit "$ec"; fi)

just filters out SIGPIPE from return codes.