bash Shell 管道:当一个命令失败时立即退出
声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow
原文地址: http://stackoverflow.com/questions/8976139/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me):
StackOverFlow
Shell pipe: Exit immediately when one command fails
提问by Tobi
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?
我在 bash 中使用了几个命令的管道。如果其中一个命令失败,有没有办法配置 bash 以立即终止整个管道中的所有命令?
In my case, the first command, say command1, runs for a while until it produces some output. You might substitute command1by (sleep 5 && echo "Hello"), for instance.
在我的例子中,第一个命令,比如说command1,运行一段时间,直到它产生一些输出。您可以替代command1的(sleep 5 && echo "Hello"),例如。
Now, command1 | falsedoes fail after 5 seconds but not immediately.
现在,command1 | false确实会在 5 秒后失败,但不会立即失败。
This behavior seems to have something to do with the amount of output the command produces. For instance, find / | falsereturns immediately.
这种行为似乎与命令产生的输出量有关。例如,find / | false立即返回。
In general, I wonder why bash behaves like this. Can anyone imagine any situation where it is useful that code like command1 | non-existing-commanddoes not exit at once?
一般来说,我想知道为什么 bash 会这样。任何人都可以想象任何情况下,像这样的代码command1 | non-existing-command不会立即退出是有用的吗?
PS: Using temporary files is not an option for me, as the intermediate results I pipe around are to big to be stored.
PS:使用临时文件对我来说不是一个选择,因为我传递的中间结果太大而无法存储。
PPS: Neither set -enor set -o pipefailseem to influence this phenomenon.
PPS:既不影响set -e也不set -o pipefail似乎影响这种现象。
采纳答案by Marcel M
The bash documentation says in its section about pipelines:
bash 文档在其有关管道的部分中说:
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.
“在它自己的子 shell 中”意味着产生一个新的 bash 进程,然后它可以执行实际的命令。每个子shell 都成功启动,即使它立即确定要求它执行的命令不存在。
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 hellowill throw a "command not found" error, but the file hellowill be created nonetheless.
这就解释了为什么即使其中一个命令是无意义的,也可以成功设置整个管道。Bash 不会检查每个命令是否可以运行,而是将其委托给子 shell。这也解释了为什么,例如,该命令nonexisting-command | touch hello会抛出“未找到命令”错误,但hello仍会创建该文件。
In the same section, it also says:
在同一部分,它还说:
The shell waits for all commands in the pipeline to terminate before returning a value.
shell 在返回值之前等待管道中的所有命令终止。
In sleep 5 | nonexisting-command, as A.H. pointed out, the sleep 5terminates after 5 seconds, not immediately, hence the shell will also wait 5 seconds.
在 中sleep 5 | nonexisting-command,正如 AH 所指出的,sleep 5在 5 秒后终止,而不是立即终止,因此 shell 也将等待 5 秒。
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:
无论如何,一种稍微难看的解决方法是使用 FIFO:
mkfifo myfifo
./long-running-script.sh > myfifo &
whoops-a-typo < myfifo
Here, the long-running-script.shis 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.
在这里,long-running-script.sh启动然后脚本在下一行立即失败。使用多个 FIFO,这可以扩展到具有两个以上命令的管道。
回答by Alan
The first program does not know whether the second is terminated or not until it tries to write some date into the pipe. In case the second is terminated, the first receives the SIGPIPE which usually causes immediate exit.
第一个程序不知道第二个程序是否终止,直到它尝试将某个日期写入管道。如果第二个终止,第一个接收通常会导致立即退出的 SIGPIPE。
You can force the first line of output to be piped immediately after staring, like this:
您可以在凝视后立即强制第一行输出通过管道传输,如下所示:
(sleep 0.1; echo; command1) | command2
This 100ms sleep is intended to wait until possible command2 exit right after starting. Of course, if command2 exits after 2 seconds, and command1 will be silent for 60 seconds, the whole shell command will return only after 60.1 seconds.
这个 100 毫秒的睡眠旨在等待,直到可能的 command2 在启动后立即退出。当然,如果 command2 在 2 秒后退出,并且 command1 将静默 60 秒,则整个 shell 命令只会在 60.1 秒后返回。
回答by Dan
sleep 5doesn't produce any output until it finishes, while find /immediately produces output that bash attempts to pipe to false.
sleep 5在完成之前不会产生任何输出,而会find /立即产生 bash 尝试通过管道传输到false.
回答by A.H.
find / |falsefails faster because the first write(2)system call from findfails with the error EPIPE(Broken pipe). This is because falsehas been already terminated and hence the pipe between these two commands has been closed already on one side.
find / |false失败更快,因为第一个write(2)系统调用find失败并出现错误EPIPE(管道损坏)。这是因为false已经终止,因此这两个命令之间的管道已经在一侧关闭。
If findwould ignore that error (it could do so in theory) it would by also "fail slow".
如果find忽略该错误(理论上可以这样做),它也会“失败缓慢”。
(sleep 5 && echo "Hello") | falseis "fail slow", because the first part, sleep, does not "test" the pipe by writing to it. After 5 seconds the echoalso get the EPIPEerror. Whether this error terminates the first part in this case or not is not important to the question.
(sleep 5 && echo "Hello") | false是“失败缓慢”,因为第一部分sleep,不会通过写入管道来“测试”管道。5 秒后echo也得到EPIPE错误。在这种情况下,此错误是否终止第一部分对于问题并不重要。
回答by jarno
The following code seems to work by Dash, but EXIT traps within pipe do not work in Bash; maybe it is a bug in Bash.
以下代码似乎适用于 Dash,但管道内的 EXIT 陷阱在 Bash 中不起作用;也许这是 Bash 中的一个错误。
#!/bin/sh
echo PID of the shell: $$
trap 'echo In INT trap >&2; trap - EXIT INT; kill -s INT $$' INT
(
# now in subshell
pidofsubshell=$(exec sh -c 'echo "$PPID"')
# $BASHPID can be used as a value, when using Bash
echo PID of subshell: $pidofsubshell
fifo=$(mktemp -u); shells=$(mktemp) childs=$(mktemp)
mkfifo $fifo
trap 'echo In sub trap >&2; rm $fifo $shells $childs; trap - EXIT; exit' EXIT HUP TERM INT ALRM
pipe_trap() {
code=$?
echo In sub sub trap >&2
echo $code >> $fifo
}
{ trap 'echo In pipe signal trap >&2; kill $(cat $childs $shells) 2>/dev/null' INT HUP TERM ALRM
{ trap 'pipe_trap 1' EXIT
sleep 30; } \
| { trap 'pipe_trap 2' EXIT
sleep 50 & sleep 2; } \
| { trap 'pipe_trap 3' EXIT
sleep 40; } &
}
echo ps tail:
ps xao pid,ppid,pgid,sid,command | head -n 1
ps xao pid,ppid,pgid,sid,command | tail -n 15
ps -o pid= --ppid $pidofsubshell | head -n -2 > $shells # strip pids of ps and head
echo shells:
cat $shells
while read -r ppid; do ps -o pid= --ppid $ppid; done <$shells >$childs
echo childs of above
cat $childs
{
IFS=' ' read -r id exitcode
echo Pipe part nr. $id terminated first with code $exitcode\; killing the remaining processes.
kill $(cat $childs $shells) 2>/dev/null
} < $fifo
)
echo
echo After subshell:
ps xao pid,ppid,pgid,sid,command | head -n 1
ps xao pid,ppid,pgid,sid,command | tail -n 15

