Bash exit status used with PIPE

6,686

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

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.

Share:
6,686

Related videos on Youtube

sshekhar1980
Author by

sshekhar1980

Updated on September 18, 2022

Comments

  • sshekhar1980
    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 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?

    • Admin
      Admin about 7 years
      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.
    • Admin
      Admin about 7 years
      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.
    • Admin
      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
      Admin about 7 years
      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. :)
    • Admin
      Admin about 7 years
      yeah but start of execution doesn't matter, what's important is the end; the last command starts before the first one ends
    • Admin
      Admin about 7 years
      Consider an example where the first command never ends, like tail -f filename | echo $?. The shell has to replace the $? variable before waiting for the tail command to finish.
  • sshekhar1980
    sshekhar1980 about 7 years
    Thank you Gilles, I wasn't aware of these complexities involved. This really clarified this further for me.
  • ilkkachu
    ilkkachu about 7 years
    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.
  • eckes
    eckes about 7 years
    Upvote for PIPESTATUS and pipefiail, but the |exho is really confusing