Why exit code 141 with grep -q?
Asked Answered
A

4

86

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.

Arita answered 1/10, 2013 at 15:31 Comment(0)
O
127

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.

Ox answered 1/10, 2013 at 15:51 Comment(11)
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.Rumilly
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.Arita
@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: unix.stackexchange.com/a/99134/9041Hesitate
@SandraSchlichting: See Effective SIGPIPE handling for issues with pipefail in bash and alternative to it.Irrefutable
@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.Two
@Rumilly This just made so much sense to me, after years of never actually going and looking for that answer. Thank you!Xenophon
To avoid exit 141 with head -c (counting bytes), replace it with cut -b start_byte-end_byte.Ambient
An interesting if verbose solution to SIGPIPE and pipefail is presented here: unix.stackexchange.com/a/582850/66344Lasseter
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.Paederast
An alternative solution to a child process exiting with 141 (SIGPIPE) is shown hereTrope
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.Engage
V
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.

Vizza answered 1/10, 2013 at 15:39 Comment(0)
P
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.

Poor answered 2/6, 2019 at 0:20 Comment(3)
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.Engage
@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.Gadoid
Actually that's a good answer: head -1 <(seq 1 10000) the return code is 0Sadomasochism
M
0

You can just continue eating the output, e.g.:

command | { head -n1; cat >/dev/null; }
Marshland answered 26/10, 2023 at 8:27 Comment(3)
Though then wty would you not simply drop the -q and the cat? grep whatever >/dev/nullTatiania
You are right, this does not make sense, but for the head -n1 it works. I will try to edit my answer.Marshland
This approach is also great for less, which is often exited before stdout is fully consumed. command | { less; cat >/dev/null; }Deserted

© 2022 - 2024 — McMap. All rights reserved.