![bash:如何传播进程替换中的错误?](https://linux22.com/image/69454/bash%EF%BC%9A%E5%A6%82%E4%BD%95%E4%BC%A0%E6%92%AD%E8%BF%9B%E7%A8%8B%E6%9B%BF%E6%8D%A2%E4%B8%AD%E7%9A%84%E9%94%99%E8%AF%AF%EF%BC%9F.png)
我希望每当使用 shell 脚本执行的命令失败时,它们都会失败。
通常我会这样做:
set -e
set -o pipefail
(通常我set -u
也会添加)
问题是,上述方法都不适用于进程替换。此代码打印“ok”并退出并返回代码 = 0,而我希望它失败:
#!/bin/bash -e
set -o pipefail
cat <(false) <(echo ok)
除了进程替换之外,是否有相当于“pipefail”的东西?还有其他方法可以将命令的输出作为文件传递给命令,但每当这些程序中的任何一个失败时都会引发错误吗?
穷人的解决方案是检测这些命令是否写入 stderr(但某些命令在成功的情况下会写入 stderr)。
另一个更符合 posix 标准的解决方案是使用命名管道,但我需要将那些使用进程替换的命令启动为从编译代码动态构建的单行程序,并且创建命名管道会使事情变得复杂(额外的命令、捕获错误)删除它们等)
答案1
您只能通过以下方式解决该问题:
cat <(false || kill $$) <(echo ok)
other_command
SIGTERM
在执行第二个命令之前,脚本的子 shell已完成 ( other_command
)。该echo ok
命令“有时”执行:问题是进程替换是异步的。不保证kill $$
命令一定会执行前或者后命令echo ok
。这是操作系统调度的问题。
考虑这样的 bash 脚本:
#!/bin/bash
set -e
set -o pipefail
cat <(echo pre) <(false || kill $$) <(echo post)
echo "you will never see this"
该脚本的输出可以是:
$ ./script
Terminated
$ echo $?
143 # it's 128 + 15 (signal number of SIGTERM)
或者:
$ ./script
Terminated
$ pre
post
$ echo $?
143
您可以尝试一下,尝试几次后,您将在输出中看到两种不同的顺序。在第一个命令中,脚本在其他两个echo
命令写入文件描述符之前终止。在第二个命令中,false
或kill
命令可能安排在echo
命令之后。
或者更准确地说:将信号发送到 shell 进程的实用程序的系统调用signal()
的调度(或传递)晚于或早于 echo系统调用。kill
SIGTERM
write()
但是,脚本停止并且退出代码不为 0。因此它应该可以解决您的问题。
另一种解决方案当然,为此使用命名管道。但是,这取决于您的脚本实现命名管道或上述解决方法的复杂程度。
参考:
答案2
作为记录,即使答案和评论很好并且有帮助,我最终还是实现了一些不同的东西(我在父进程中接收信号有一些限制,我在问题中没有提到)
基本上,我结束了这样的事情:
command <(subcomand 2>error_file && rm error_file) <(....) ...
然后我检查错误文件。如果存在,我就知道哪个子命令失败了(并且 error_file 的内容可能有用)。比我最初想要的更加冗长和黑客,但比在一行 bash 命令中创建命名管道要简单。
答案3
这个例子展示了如何kill
与 一起使用trap
。
#! /bin/bash
failure ()
{
echo 'sub process failed' >&2
exit 1
}
trap failure SIGUSR1
cat < <( false || kill -SIGUSR1 $$ )
但kill
无法将返回码从子进程传递到父进程。
答案4
我发现的最可靠的方法是将子进程的错误代码存储在临时文件中,就像在函数上下文中一样:
function() {
ERR=$(mktemp)
VAR=$(some_command; echo $? > $ERR)
if [[ $(cat $ERR && rm $ERR) -gt 0 ]]; then
echo "An error occurred in the sub shell" > /dev/stderr
return 1
fi
}