这个问题类似于下面的问题关联,但重点关注使用命令行(bash shell)。
使用一个简单的示例,执行以下命令时:
$ cat <(date); echo $?
Fri Jul 7 21:04:38 UTC 2017
0
正如预期的那样,退出值为 0。
下面的命令中故意引入了一个错误,但返回值仍然是0:
$ cat <(datE); echo $?
bash: datE: command not found...
Similar command is: 'date'
0
有没有办法捕获在命令行上运行时进程替换中出现的错误(即不必将其放入脚本中)?
上面包含的链接中的解决方案会终止正在运行该命令的脚本。
答案1
在 中bash
,进程替换作为后台作业启动。您可以使用以下命令获取最后一个进程的主导进程的 pid $!
,并且可以使用与wait thatpid
其他后台作业类似的方式获取其退出状态:
$ bash -c 'cat <(exit 3); wait "$!"; echo "$?"'
3
diff <(cmd1) <(cmd2)
现在,如果您需要在同一个命令中使用两个进程替换(如 中所示) ,那么这将无济于事。
$ bash -c 'cat <(exit 3) <(exit 4); wait "$!"; echo "$?"'
4
上面的pidexit 3
丢失了
它可以通过这种技巧恢复:
unset -v p1 p2
x='!'
cat <(exit 3) ${!x#${p1=$!}} <(exit 4) ${!x#${p2=$!}}
其中两个 pid 都存储在$p1
和中$p2
,但这没有用,因为只有最后一个被插入到 shell 的作业表中,并且wait
将拒绝等待$p1
错误地声称它不是这个 shell 的子级,即使$p1
尚未终止:
$ cat test-script
unset -v p1 p2
x='!'
set -o xtrace
cat <(echo x; exec >&-; sleep 1; exit 3) ${!x#${p1=$!}} <(exit 4) ${!x#${p2=$!}}
ps -fH
wait "$p1"; echo "$?"
wait "$p2"; echo "$?"
$ bash test-script
++ echo x
++ exec
++ sleep 1
+ cat /dev/fd/63 /dev/fd/62
++ exit 4
x
+ ps -fH
UID PID PPID C STIME TTY TIME CMD
chazelas 15393 9820 0 21:44 pts/4 00:00:00 /bin/zsh
chazelas 17769 15393 0 22:19 pts/4 00:00:00 bash test-script
chazelas 17770 17769 0 22:19 pts/4 00:00:00 bash test-script
chazelas 17772 17770 0 22:19 pts/4 00:00:00 sleep 1
chazelas 17776 17769 0 22:19 pts/4 00:00:00 ps -fH
+ wait 17770
test-script: line 6: wait: pid 17770 is not a child of this shell
+ echo 127
127
+ wait 17771
+ echo 4
4
$ ++ exit 3
更多相关信息,请访问@mosvy 的回答到运行异步任务并在 bash 中检索其退出代码和输出
答案2
使用进程替换的另一种方法是使用/dev/stdin
文件 arg ,以便管道按预期工作:
set -o pipefail
datE | cat /dev/stdin
上面的例子有点做作,因为cat
如果没有给定文件参数,将从 stdin 读取。当必须向命令提供文件时,它很有用。
答案3
没有惯用的方法可以使调用进程 ( bash
) 将错误传播到cat
,因为它们是单独启动的。连接cat
和 的唯一东西datE
可能是一个名为 的临时文件,/dev/fd/63
其生命周期与包含 shell 语句的生命周期相关。
我知道的最简单的修复是将输出放入临时文件中,并检查dateE
.
就像是:
# Make a tempfile
tmpfile=$(mktemp)
# Delete the tempfile when this shell process exits
trap "rm ${tmpfile}" 0
# Run the command and check for success
if datE > "${tmpfile}"; then
cat < "${tmpfile}"
else
# Report the error.
fi
话虽如此,有一些黑客方法可能涉及用于传播错误值的其他文件。但一般的想法是,进程不能(也不应该)改变它们的父进程或同级进程,除非存在一些共同商定的共享状态(如文件、fifo 队列或管道等)。
答案4
在你的例子中:
cat <(datE); echo $?
发生的情况是datE
抛出错误并且不生成任何输出。然后它会抛出一个错误代码。然而,(空)输入随后被呈现给cat
它,它高兴地什么也没有咀嚼,现在你的退出代码为零。
如果去掉中间步骤,它会按您的预期工作:
$ datE; echo $?
datE: command not found
127
如果您想bash
中止管道中的任何故障和任何未捕获的错误,请运行以下两个命令:
set -e
set -o pipefail
其他 shell 可能提供类似的设置。