如何使用进程替换来检测错误

如何使用进程替换来检测错误

这个问题类似于下面的问题关联,但重点关注使用命令行(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 可能提供类似的设置。

相关内容