【问题标题】:Get exit code of process substitution with pipe into while loop使用管道获取进程替换的退出代码进入while循环
【发布时间】:2017-09-29 20:48:35
【问题描述】:

以下脚本调用另一个程序在 while 循环中读取其输出(请参阅 Bash - How to pipe input to while loop and preserve variables after loop ends):

while read -r col0 col1; do
    # [...]
done < <(other_program [args ...])

如何检查other_program 的退出代码以查看循环是否正确执行?

【问题讨论】:

  • 签到?您想将状态写入文件(或)在标准输出中查看吗?

标签: bash process-substitution


【解决方案1】:

注意:ls -d / /nosuch 用作下面的示例命令,因为它失败(退出代码 1)同时仍然产生 stdout 输出(/)(除了 stderr 输出)。

Bash v4.2+ 解决方案:

ccarton's helpful answer 原则上运行良好,但默认情况下while 循环在 subshel​​l 中运行,这意味着在循环中创建或修改的任何变量对 当前外壳。

Bash v4.2+ 中,您可以通过打开 lastpipe 选项 来更改此设置,这会使 最后 段在 current shell 中运行的管道;
正如 ccarton 的回答,必须将 pipefail 选项 设置为让 $? 反映管道中第一个 failing 命令的退出代码:

shopt -s lastpipe  # run the last segment of a pipeline in the current shell
shopt -so pipefail # reflect a pipeline's first failing command's exit code in $?

ls -d / /nosuch | while read -r line; do 
  result=$line
done

echo "result: [$result]; exit code: $?"

以上产量(stderr 输出省略):

result: [/]; exit code: 1

如您所见,while 循环中设置的$result 变量可用,ls 命令的(非零)退出代码反映在$? 中。


Bash v3+ 解决方案:

ikkachu's helpful answer 效果很好,展示了先进的技术,但是有点麻烦。
这是一个更简单的选择:

while read -r line || { ec=$line && break; }; do   # Note the `|| { ...; }` part.
    result=$line
done < <(ls -d / /nosuch; printf $?)               # Note the `; printf $?` part.

echo "result: [$result]; exit code: $ec"
    1234563在最后一个循环操作中,但表示失败(退出代码1),通常会退出循环。
  • 我们可以用||检测这种情况,并将退出代码(仍然读入$line)分配给变量$ec,然后退出循环。


如果命令的输出没有尾随 \n,则需要做更多工作:

while read -r line || 
  { [[ $line =~ ^(.*)/([0-9]+)$ ]] && ec=${BASH_REMATCH[2]} && line=${BASH_REMATCH[1]};
    [[ -n $line ]]; }
do
    result=$line
done < <(printf 'no trailing newline'; ls /nosuch; printf "/$?")

echo "result: [$result]; exit code: $ec"

以上产量(stderr 输出省略):

result: [no trailing newline]; exit code: 1

【讨论】:

  • 目前还不知道shopt -s lastpipe。到目前为止,我一直在使用进程替换作为替代方案,但今天我意识到set -e 在进程替换中出现错误时不会导致 shell 中止,因此使用lastpipe 切换回看起来更自然的管道方法选项。
【解决方案2】:

至少一种方法是通过命名管道重定向后台进程的输出。这将允许获取其 PID,然后通过 PID 上的waiting 获取退出状态。

#!/bin/bash
mkfifo pipe || exit 1
(echo foo ; exit 19)  > pipe &
pid=$!
while read x ; do echo "read: $x" ; done < pipe
wait $pid
echo "exit status of bg process: $?"
rm pipe

如果您可以使用直接管道(即不介意在子 shell 中运行循环),您可以使用 Bash 的 PIPESTATUS,其中包含管道中所有命令的退出代码:

(echo foo ; exit 19) | while read x ; do 
  echo "read: $x" ; done; 
echo "status: ${PIPESTATUS[0]}" 

【讨论】:

    【解决方案3】:

    另一种方法是使用coproc(需要4.0+)。

    coproc other_program [args ...]
    while read -r -u ${COPROC[0]} col0 col1; do
        # [...]
    done
    wait $COPROC_PID || echo "Error exit status: $?"
    

    coproc 使您不必设置异步和 stdin/stdout 重定向,否则您需要在等效的mkfifo 中执行这些操作。

    【讨论】:

    • 这很好用,但是应该提到必须将$COPROC_PID 保存到其他变量,因为当命令终止时它将被取消设置,导致wait 在没有PID 的情况下被调用.有关详细信息,请参阅unix.stackexchange.com/a/338022。还应该提到的是,当执行完成速度非常快的命令时,这种方法可能会失败,例如cat &lt;somefile&gt;。在这种情况下,命令在进入循环之前完成,导致read 尝试从不再存在的文件描述符${COPROC[0]} 中读取。
    【解决方案4】:

    一种简单的方法是使用 bash pipefail 选项从管道传播第一个错误代码。

    set -o pipefail
    other_program | while read x; do
            echo "Read: $x"
    done || echo "Error: $?"
    

    【讨论】:

    • 做得很好,但值得一提的是while 循环随后在子shell 中运行,并且它的变量对当前shell 不可见。在 Bash v4.2+ 中,您可以使用 shopt -s lastpipe 来防止这种情况发生。
    猜你喜欢
    • 2016-09-04
    • 1970-01-01
    • 2019-08-18
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2021-03-17
    • 1970-01-01
    相关资源
    最近更新 更多