I'm using a pipe of several commands in bash. Is there a way of configuring bash to terminate all commands in the whole pipeline immediately should one of the commands fail?
In my case, the first command, say command1
, runs for a while until it produces some output. You might substitute command1
by (sleep 5 && echo "Hello")
, for instance.
Now, command1 | false
does fail after 5 seconds but not immediately.
This behavior seems to have something to do with the amount of output the command produces. For instance, find / | false
returns immediately.
In general, I wonder why bash behaves like this. Can anyone imagine any situation where it is useful that code like command1 | non-existing-command
does not exit at once?
PS: Using temporary files is not an option for me, as the intermediate results I pipe around are to big to be stored.
PPS: Neither set -e
nor set -o pipefail
seem to influence this phenomenon.
Exit When Any Command Fails This can actually be done with a single line using the set builtin command with the -e option. Putting this at the top of a bash script will cause the script to exit if any commands return a non-zero exit code.
Bash Pitfalls Failed commands do not stop script execution In most scripting languages, if a function call fails, it may throw an exception and stop execution of the program. Bash commands do not have exceptions, but they do have exit codes.
set -o pipefail causes a pipeline (for example, curl -s https://sipb.mit.edu/ | grep foo ) to produce a failure return code if any command errors. Normally, pipelines only return a failure if the last command errors. In combination with set -e , this will make your script exit if any command in a pipeline errors.
The pipe character | is used to connect the output from one command to the input of another. > is used to redirect standard output to a file.
The bash documentation says in its section about pipelines:
Each command in a pipeline is executed in its own subshell [...]
"In its own subshell" means that a new bash process is spawned, which then gets to execute the actual command. Each subshell starts successfully, even when it immediately determines that the command it is asked to execute doesn't exist.
This explains why the entire pipe can be set up successfully even when one of the commands is nonsense. Bash does not check if each command can be run, it delegates that to the subshells. That also explains why, for example, the command nonexisting-command | touch hello
will throw a "command not found" error, but the file hello
will be created nonetheless.
In the same section, it also says:
The shell waits for all commands in the pipeline to terminate before returning a value.
In sleep 5 | nonexisting-command
, as A.H. pointed out, the sleep 5
terminates after 5 seconds, not immediately, hence the shell will also wait 5 seconds.
I don't know why the implementation was done this way. In cases like yours, the behavior is surely not as one would expect.
Anyway, one slightly ugly workaround is to use FIFOs:
mkfifo myfifo ./long-running-script.sh > myfifo & whoops-a-typo < myfifo
Here, the long-running-script.sh
is started and then the scripts fails immediately on the next line. Using mutiple FIFOs, this could be extended to pipes with more than two commands.
sleep 5
doesn't produce any output until it finishes, while find /
immediately produces output that bash attempts to pipe to false
.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With