8

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 locate lss 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?

  • 2
    All components of a pipeline run at the same time. Thus, in which lss | echo $?, the echo is started before which has exited; the shell generating the command line for echo has no possible way of knowing what the exit status of which will later be. – Charles Duffy May 20 '17 at 17:13
  • They 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. – Tim S. May 20 '17 at 18:21
  • 3
    @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. – Random832 May 20 '17 at 19:37
  • I 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. :) – Tim S. May 20 '17 at 21:10
  • yeah but start of execution doesn't matter, what's important is the end; the last command starts before the first one ends – dn3s May 21 '17 at 5:59
12

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
  • @sshekhar1980 Good! If this solves your issue, please consider accepting the answer. If you're happy with the answer, accepting it and maybe even upvoting it is the best way to say "Thank You!" :-) – Kusalananda May 25 '17 at 8:16
10

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 (or setopt 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 $?).

  • Thank you Gilles, I wasn't aware of these complexities involved. This really clarified this further for me. – sshekhar1980 May 20 '17 at 14:19
4

Yes PIPE produce an exit status, if you want the exit code of the command before the PIPE you can use $PIPESTATUS

According to this 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 seems PIPESTATUS only work when 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 ${PIPESTATUS1} Check this for more information.

  • 2
    While PIPESTATUS contains the exit statuses of the commands in the last pipeline, your first example doesn't make any more sense than somecmd | echo $?, which Kusalananda dealt with in their answer. false; true | echo $PIPESTATUS prints 1 for the exit status of false. – ilkkachu May 20 '17 at 15:20
  • Upvote for PIPESTATUS and pipefiail, but the |exho is really confusing – eckes May 20 '17 at 22:16
0

The shell evaluates the command line before the pipeline is executed. So $? is the value of the last command executed before the pipeline. Whether echo itself is started before or after which is irrelevant.

Now if your question was specifically about the propagation of the exit status, you can study the default behavior like this:

% false | true
% echo $?
0

% true | false
% echo $?
1

As you can see, the exit status of a pipeline is the exit status of its last command.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.