【问题标题】:Shell pipe: Exit immediately when one command failsShell pipe:当一个命令失败时立即退出
【发布时间】:2012-02-17 01:38:37
【问题描述】:

我在 bash 中使用了多个命令的管道。如果其中一个命令失败,是否可以配置 bash 以立即终止整个管道中的所有命令?

在我的例子中,第一个命令,比如command1,会运行一段时间,直到它产生一些输出。例如,您可以将command1 替换为(sleep 5 && echo "Hello")

现在,command1 | false 确实会在 5 秒后失败,但不会立即失败。

这种行为似乎与命令产生的输出量有关。例如,find / | false 立即返回。

总的来说,我想知道为什么 bash 会这样。谁能想象像command1 | non-existing-command 这样的代码不会立即退出的任何情况?

PS:使用临时文件对我来说不是一个选项,因为我传输的中间结果太大而无法存储。

PPS:set -eset -o pipefail 似乎都不会影响这种现象。

【问题讨论】:

标签: bash pipe


【解决方案1】:

sleep 5 在完成之前不会产生任何输出,而find / 会立即产生 bash 尝试通过管道传输到 false 的输出。

【讨论】:

  • @Jaypal:我同意,这个例子可能会产生误导。我编辑了帖子,希望现在更清楚。
  • @Dan:是的,但这并不能真正回答我的问题。我想知道一旦一个命令失败,我是否可以让 bash 终止管道中的所有命令。
【解决方案2】:

find / |false 失败更快,因为来自find 的第一个write(2) 系统调用失败并出现错误EPIPE(管道损坏)。这是因为false 已经终止,因此这两个命令之间的管道已经在一侧关闭。

如果find 会忽略该错误(理论上它可以这样做),它也会“失败缓慢”。

(sleep 5 && echo "Hello") | false 是“失败缓慢”,因为第一部分 sleep 不会通过写入管道来“测试”管道。 5 秒后,echo 也会出现 EPIPE 错误。在这种情况下,这个错误是否终止了第一部分对于这个问题并不重要。

【讨论】:

    【解决方案3】:

    bash 文档在其section about pipelines 中说:

    管道中的每个命令都在其自己的子外壳中执行 [...]

    “In its own subshel​​l”意味着产生了一个新的 bash 进程,然后它开始执行实际的命令。每个子shell 都成功启动,即使它立即确定要求执行的命令不存在。

    这解释了为什么即使其中一个命令是废话,也可以成功设置整个管道。 Bash 不会检查每个命令是否可以运行,它会将其委托给子 shell。这也解释了为什么,例如,命令 nonexisting-command | touch hello 会抛出“找不到命令”错误,但仍会创建文件 hello

    在同一节中,它还说:

    shell 在返回值之前等待管道中的所有命令终止。

    sleep 5 | nonexisting-command 中,正如 A.H. 指出的那样,sleep 5 在 5 秒后终止,而不是立即终止,因此 shell 也会等待 5 秒。

    我不知道为什么要这样实现。在像您这样的情况下,这种行为肯定不是人们所期望的。

    无论如何,一个稍微难看的解决方法是使用 FIFO:

    mkfifo myfifo
    ./long-running-script.sh > myfifo &
    whoops-a-typo < myfifo
    

    在这里,long-running-script.sh 启动,然后脚本在下一行立即失败。使用多个 FIFO,这可以扩展到具有两个以上命令的管道。

    【讨论】:

      【解决方案4】:

      第一个程序不知道第二个程序是否终止,直到它尝试将某个日期写入管道。如果第二个被终止,第一个会收到 SIGPIPE,这通常会导致立即退出。

      你可以强制输出的第一行在注视后立即通过管道传输,如下所示:

      (sleep 0.1; echo; command1) | command2
      

      这个 100 毫秒的睡眠是为了等到 command2 在启动后立即退出。 当然,如果 command2 2 秒后退出,command1 会静默 60 秒,整个 shell 命令只会在 60.1 秒后返回。

      【讨论】:

        【解决方案5】:

        以下代码似乎对 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 $1 >&2
                echo $1 $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
        

        【讨论】:

          猜你喜欢
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 2019-12-04
          • 1970-01-01
          • 2021-09-24
          • 1970-01-01
          • 2017-03-08
          相关资源
          最近更新 更多