76

Can someone explain why I get exit code 141 from the below?

#!/usr/bin/bash

set -o pipefail

zfs list | grep tank
echo a ${PIPESTATUS[@]}

zfs list | grep -q tank
echo b ${PIPESTATUS[@]}

cat /etc/passwd | grep -q root
echo c ${PIPESTATUS[@]}

I get

...
a 0 0
b 141 0
c 0 0

From my understanding exit code 141 is a failure, but the line above gives zero, so it should be success, I would say.

Sandra Schlichting
  • 25,050
  • 33
  • 110
  • 162

3 Answers3

112

This is because grep -q exits immediately with a zero status as soon as a match is found. The zfs command is still writing to the pipe, but there is no reader (because grep has exited), so it is sent a SIGPIPE signal from the kernel and it exits with a status of 141.

Another common place where you see this behaviour is with head. e.g.

$ seq 1 10000 | head -1
1

$ echo ${PIPESTATUS[@]}
141 0

In this case, head read the first line and terminated which generated a SIGPIPE signal and seq exited with 141.

See "The Infamous SIGPIPE Signal" from The Linux Programmer's Guide.

Community
  • 1
  • 1
dogbane
  • 266,786
  • 75
  • 396
  • 414
  • 45
    Conventionally, an exit status *N* greater than 128 indicates the program was terminated by signal *N* - 128. Since `SIGPIPE` is signal 13, 141 - 128 = 13 indicates your program was ended by a `SIGPIPE`. – chepner Oct 01 '13 at 16:15
  • 20
    Is there a way I can still use `set -o pipefail` and `grep -q`, as I would like to keep it, as I have lots of parsing from SSH. – Sandra Schlichting Oct 01 '13 at 16:22
  • 4
    @chepner: It's not a matter of convention, it's matter of how shells deal with processes that exit due to a signal. For bash, it's 128 + signal_number, but other shells use other formulas. See this excellent post: http://unix.stackexchange.com/a/99134/9041 – Flimm Aug 13 '14 at 09:46
  • 3
    @SandraSchlichting: See [Effective SIGPIPE handling](http://www.pixelbeat.org/programming/sigpipe_handling.html) for issues with pipefail in bash and alternative to it. – akhan May 24 '16 at 19:50
  • 4
    @SandraSchlichting: I have the same issue and it is very annoying. One idea: Do not use `-q` option with `grep`, but redirect output: `1> /dev/null 2>&1`. In theory, slightly slower as `grep` will process entire input, but, practically: it works. – kevinarpe May 08 '17 at 10:15
  • @chepner This just made so much sense to me, after years of never actually going and looking for that answer. Thank you! – Tom Granot May 16 '19 at 16:58
  • 1
    To avoid exit 141 with `head -c` (counting bytes), replace it with `cut -b start_byte-end_byte`. – Victor Sergienko May 22 '19 at 19:55
  • 2
    An interesting if verbose solution to `SIGPIPE` and `pipefail` is presented here: https://unix.stackexchange.com/a/582850/66344 – Tad Lispy Jun 22 '20 at 07:57
  • If you are just wanting the first line of the result, then `head -n 1` can be replaced with `sed -n 1p` which doesn't have this failure. – balupton Jul 03 '21 at 18:38
  • An alternative solution to a child process exiting with 141 (SIGPIPE) is [shown here](https://stackoverflow.com/a/72985727/1329892) – SpinUp __ A Davis Jul 14 '22 at 19:30
  • My solution to this is to assign the producer's output to a variable, then `echo` the variable to `grep -q`. It seems that `echo` doesn't exit with error if the pipe is closed. – Marcus Dec 18 '22 at 00:59
6

I'm not familiar with zfs list, but I guess it complains about its standard output being closed - grep -q exits immediately when a match is found, unlike grep.

volferine
  • 372
  • 1
  • 9
3

Another option would be to not use a pipe, but use a process substitution:

grep -q tank <(zfs list)

Update: I guess is the same thing, as the process run inside parentheses will also receive sigpipe.

  • If looking for a simple solution, I think a very simple one is to assign the output of the producer to a variable, and `echo` that variable to grep. It seems that `echo` doesn't exit with error when `grep -q` closes the pipe. – Marcus Dec 18 '22 at 00:57
  • 1
    @Marcus: It would be worth noting that the output of the producer is no longer streamed when using an intermediate variable. The output of the producer is held all at once in memory in the variable, and `grep`'s processing can start only after the producer completes. – nishanthshanmugham Feb 17 '23 at 15:37