Not possible with basic shell constructs, probably not possible in shell at all.
Your first example doesn't do what you think. echo
doesn't use standard input, so putting it on the right side of a pipe is never a good idea. The $?
that you're echoing is not the exit value of the grep 0
. All commands in a pipeline run simultaneously. echo
has already been started, with the existing value of $?
, before the other commands in the pipeline have finished. It echoes the exit value of whatever you did before the pipeline.
# The first command is to set things up so that $? is 2 when the
# second command is parsed.
$ sh -c 'exit 2'
$ echo 1|grep 0|echo $?
2
Your second example is a little more interesting. It's correct to say that wc
is unfazed by grep
's exit status. All commands in the pipeline are children of the shell, so their exit statuses are reported to the shell. The wc
process doesn't know anything about the grep
process. The only communication between them is the data stream written to the pipe by grep
and read from the pipe by wc
.
There are ways to find all the exit statuses after the fact (the linked question in the comment by shx2 has examples) but a basic rule that you can't avoid is that the shell will always wait for all the commands to finish.
Early exits in a pipeline sometimes do have a cascade effect. If a command on the right side of a pipe exits without reading all the data from the pipe, the command on the left of that pipe will get a SIGPIPE
signal the next time it tries to write, which by default terminates the process. (The 2 phrases to pay close attention to there are "the next time it tries to write" and "by default". If a the writing process spends a long time doing other things between writes to the pipe, it won't die immediately. If it handles the SIGPIPE, it won't die at all.)
In the other direction, when a command on the left side of a pipe exits, the command on the right side of that pipe gets EOF, which does cause the exit to happen fairly soon when it's a simple command like wc
that doesn't do much processing after reading its input.
With direct use of pipe()
, fork()
, and wait3()
, it would be possible to construct a pipeline, notice when one child exits badly, and kill the rest of them immediately. This requires a language more sophisticated than the shell.
I tried to come up with a way to do it in shell with a series of named pipes, but I don't see it. You can run all the processes as separate jobs and get their PIDs with $!
, but the wait
builtin isn't flexible enough to say "wait for any child in this set to exit, and tell me which one it was and what the exit status was".
If you're willing to mess with ps
and/or /proc
you can find out which processes have exited (they'll be zombies), but you can't distinguish successful exit from any other kind.