使用管道获取进程替换的退出代码进入 while 循环
Get exit code of process substitution with pipe into while loop
以下脚本调用另一个程序在 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
的退出代码以查看循环是否正确执行?
至少一种方法是通过命名管道重定向后台进程的输出。这将允许获取其 PID,然后通过 wait
ing 在 PID 上获取退出状态。
#!/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
如果您可以使用直接管道(即不介意子外壳中的循环 运行),您可以使用 Bash 的 PIPESTATUS
,其中包含管道中所有命令的退出代码:
(echo foo ; exit 19) | while read x ; do
echo "read: $x" ; done;
echo "status: ${PIPESTATUS[0]}"
一种简单的方法是使用 bash pipefail
选项从管道传播第一个错误代码。
set -o pipefail
other_program | while read x; do
echo "Read: $x"
done || echo "Error: $?"
注意:ls -d / /nosuch
用作下面的示例命令,因为它失败(退出代码 1
),同时仍在生成标准输出输出(/
)(除了 stderr 输出)。
Bashv4.2+解决方案:
原则上运行良好,但默认情况下 while
循环 运行 在 subshell 中,这意味着在循环中创建或修改的任何变量将对 current shell.
不可见
在 Bash v4.2+ 中,您可以通过打开 lastpipe
选项 来更改此设置, 这使得管道的 last 段 运行 在 current shell;
正如 ccarton 的回答,pipefail
选项 必须设置为 $?
反映第一个 失败 的退出代码管道中的命令:
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: $?"
以上结果(省略标准错误输出):
result: [/]; exit code: 1
如您所见,在while
循环中设置的$result
变量可用,ls
命令的(非零)退出代码反映在$?
.
Bashv3+解决方案:
效果不错,技术先进,就是有点繁琐
这是一个更简单的选择:
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"
通过将 $?
的值、ls
命令的退出代码附加到输出 而没有尾随 \n
(printf $?
), read
在最后一个循环操作中读取它,但表示失败(退出代码 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"
以上结果(省略标准错误输出):
result: [no trailing newline]; exit code: 1
另一种方法是使用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
.
中执行这些操作
以下脚本调用另一个程序在 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
的退出代码以查看循环是否正确执行?
至少一种方法是通过命名管道重定向后台进程的输出。这将允许获取其 PID,然后通过 wait
ing 在 PID 上获取退出状态。
#!/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
如果您可以使用直接管道(即不介意子外壳中的循环 运行),您可以使用 Bash 的 PIPESTATUS
,其中包含管道中所有命令的退出代码:
(echo foo ; exit 19) | while read x ; do
echo "read: $x" ; done;
echo "status: ${PIPESTATUS[0]}"
一种简单的方法是使用 bash pipefail
选项从管道传播第一个错误代码。
set -o pipefail
other_program | while read x; do
echo "Read: $x"
done || echo "Error: $?"
注意:ls -d / /nosuch
用作下面的示例命令,因为它失败(退出代码 1
),同时仍在生成标准输出输出(/
)(除了 stderr 输出)。
Bashv4.2+解决方案:
while
循环 运行 在 subshell 中,这意味着在循环中创建或修改的任何变量将对 current shell.
在 Bash v4.2+ 中,您可以通过打开 lastpipe
选项 来更改此设置, 这使得管道的 last 段 运行 在 current shell;
正如 ccarton 的回答,pipefail
选项 必须设置为 $?
反映第一个 失败 的退出代码管道中的命令:
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: $?"
以上结果(省略标准错误输出):
result: [/]; exit code: 1
如您所见,在while
循环中设置的$result
变量可用,ls
命令的(非零)退出代码反映在$?
.
Bashv3+解决方案:
这是一个更简单的选择:
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"
通过将
$?
的值、ls
命令的退出代码附加到输出 而没有尾随\n
(printf $?
),read
在最后一个循环操作中读取它,但表示失败(退出代码 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"
以上结果(省略标准错误输出):
result: [no trailing newline]; exit code: 1
另一种方法是使用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
.