Bash exit status used with PIPE
Solution 1
The exit status of the pipeline is the exit status of the last command in the pipeline (unless the shell option pipefail
is set in the shells that support it, in which case the exit status will be that of the last command in the pipeline that exits with a non-zero status).
It is not possible to output the exit status of a pipeline from within a pipeline, since there is no way of knowing what that value would be until the pipeline has actually finished executing.
The pipeline
which lss | echo $?
is nonsensical since echo
doesn't read its standard input (pipelines are used to pass data between the output of one command to the input of the next). The echo
would also not print the exit status of the pipeline, but the exit status of the command run immediately before the pipeline.
$ false
$ which hello | echo $?
1
which: hello: Command not found.
$ true
$ which hello | echo $?
0
which: hello: Command not found.
This is a better example:
$ echo hello | read a
$ echo $?
0
$ echo nonexistent | { read filename && rm $filename; }
rm: nonexistent: No such file or directory
$ echo $?
1
This means that a pipeline may also be used with if
:
if gzip -dc file.gz | grep -q 'something'; then
echo 'something was found'
fi
Solution 2
The exit status of a pipe is the exit status of the right-hand command. The exit status of the left-hand command is ignored.
(Note that which lss | echo $?
doesn't show this. You would run which lss | true; echo $?
to show this. In which lss | echo $?
, echo $?
reports the status of the last command before this pipeline.)
The reason shells behave this way is that there is a fairly common scenario where an error in the left-hand side should be ignored. If the right-hand side exits (or more generally closes its standard input) while the left-hand side is still writing, then the left-hand side receives a SIGPIPE signal. In this case, there is usually nothing wrong: the right-hand side doesn't care about the data; if the role of the left-hand side is solely to produce this data then it's ok for it to stop.
However, if the left-hand side dies for some reason other than SIGPIPE, or if the job of the left-hand side was not solely to produce data on standard output, then an error in the left-hand side is a genuine error that should be reported.
In plain sh, the only solution is to use a named pipe.
set -e
mkfifo p
command1 >p & pid1=$!
command2 <p
wait $pid1
In ksh, bash and zsh, you can instruct the shell to make a pipeline exit with a nonzero status if any component of the pipeline exits with a nonzero status. You need to set the pipefail
option:
- ksh:
set -o pipefail
- bash:
shopt -s pipefail
- zsh:
setopt pipefail
(orsetopt pipe_fail
)
In mksh, bash and zsh, you can get the status of every component of the pipeline using the variable PIPESTATUS
(bash, mksh) or pipestatus
(zsh), which is an array containing the status of all the commands in the last pipeline (a generalization of $?
).
Solution 3
Yes, PIPE produces an exit status; if you want the exit code of the command before the PIPE, you can use $PIPESTATUS
According to this Stack Overflow Q&A, to print the exit status of a command when you use a pipe you can do:
your_command | echo $PIPESTATUS
Or set pipefail with the command set -o pipefail
(to unset it, do set +o pipefail
), and use $?
instead of $PIPESTATUS
.
your_command | echo $?
These commands only work after you run them at least one time; that means PIPESTATUS
only works when you run it after the command with the pipe, like this:
command_which_exit_10 | command_which_exit_1
echo "${PIPESTATUS[0]} ${PIPESTATUS[1]}"
You will get 10 for ${PIPESTATUS[0]}
and 1 for ${PIPESTATUS[1]}
.
See this U&L Q&A for more information.
Related videos on Youtube
sshekhar1980
Updated on September 18, 2022Comments
-
sshekhar1980 almost 2 years
I am trying to understand how exit status is communicated when a pipe is used. Suppose I am using
which
to locate a non-existent program:which lss echo $? 1
Since
which
failed to locatelss
I got an exit status of 1. This is fine. However when I try the following:which lss | echo $? 0
This indicates that the last command executed has exited normally. The only way I can understand this is that perhaps PIPE also produces an exit status. Is this right way to understand it?
-
Admin about 7 yearsAll components of a pipeline run at the same time. Thus, in
which lss | echo $?
, theecho
is started beforewhich
has exited; the shell generating the command line forecho
has no possible way of knowing what the exit status ofwhich
will later be. -
Admin about 7 yearsThey don't run at the same time. The whole purpose of the pipe is to redirect the output of one command into the other. The last command that the shell executed (ie the last one on your statement) will be the one that returns a return code.
-
Admin about 7 years@TimS. But they do run at the same time, that's how you can get the first bits of output before the first command has finished if it's a very long-running command.
-
Admin about 7 yearsI guess I was thinking about the start of the execution -- there is definite overlap, but the processes don't start together. I see what you mean though, my mistake. (The pipe is redirecting the output, not the execution...) I botched my comments, but hopefully you know what I mean. :)
-
Admin about 7 yearsyeah but start of execution doesn't matter, what's important is the end; the last command starts before the first one ends
-
Admin about 7 yearsConsider an example where the first command never ends, like
tail -f filename | echo $?
. The shell has to replace the$?
variable before waiting for thetail
command to finish.
-
-
sshekhar1980 about 7 yearsThank you Gilles, I wasn't aware of these complexities involved. This really clarified this further for me.
-
ilkkachu about 7 yearsWhile
PIPESTATUS
contains the exit statuses of the commands in the last pipeline, your first example doesn't make any more sense thansomecmd | echo $?
, which Kusalananda dealt with in their answer.false; true | echo $PIPESTATUS
prints1
for the exit status offalse
. -
eckes about 7 yearsUpvote for PIPESTATUS and pipefiail, but the
|exho
is really confusing