bash:如何在流程替换中传播错误?


19

我希望我的Shell脚本每执行一个命令失败就会失败。

通常,我这样做的方法是:

set -e
set -o pipefail

(通常我set -u也添加)

关键是上述方法都不能用于流程替换。这段代码显示“ ok”,并以返回代码= 0退出,但我希望它失败:

#!/bin/bash -e
set -o pipefail
cat <(false) <(echo ok)

除了流程替代之外,还有什么等同于“ pipefail”的东西?还有其他方法可以将命令的输出作为文件传递给命令,但是每当这些程序出现故障时都会引发错误?

穷人的解决方案是检测那些命令是否写入stderr(但在成功的情况下某些命令写入stderr)。

另一个与posix兼容的解决方案将使用命名管道,但是我需要放宽这些命令的使用过程,因为oneliners是由编译后的代码动态构建的,而创建命名管道会使事情复杂化(额外的命令,捕获错误,删除它们,等等)


您无需捕获错误即可删除命名管道。实际上,这根本不是一个好方法。考虑一下mkfifo pipe; { rm pipe; cat <file; } >pipe。该命令将挂起,直到一个阅读器打开pipe,因为它是做的外壳open(),因此只要有一个读者pipe的FS链接以pipeIS rm“d,然后cat拷贝INFILE出到外壳的描述为管。而且无论如何,如果您想从流程子中传播错误,请执行: <( ! : || kill -2 "$$")
mikeserv

感谢您删除命名管道的提示。不幸的是,该$$替换对我不起作用,因为该命令替换未完成,因为使用进程替换的命令是在从“非shell”代码(python)生成的命令管道中完成的。可能我应该在python中创建子进程,并以编程方式将它们传递给管道。
juanleon

所以用kill -2 0
mikeserv

不幸的是,“ kill -2 0”会杀死(信号)python。我不希望编写用于在多线程应用程序中执行业务逻辑的信号处理程序:-)
juanleon

如果您不想处理信号,那么为什么要尝试接收信号?无论如何,我希望命名管道最终将是最简单的解决方案,仅是因为正确执行它需要布局自己的框架并设置自己的定制调度程序。克服这一障碍,一切都变得顺利了。
mikeserv

Answers:


6

您只能通过以下方法解决该问题:

cat <(false || kill $$) <(echo ok)
other_command

脚本的子外壳SIGTERM程序在执行第二个命令(other_command)之前是d 。该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命令可以写入文件描述符之前终止。在第二个命令中,falsekill命令可能在echo命令之后进行调度。

或更精确地说:将信号发送到Shell进程signal()kill实用程序的系统调用是SIGTERM在回显write()syscall 之前或之后进行调度(或已交付)的。

但是,脚本会停止并且退出代码不为0。因此它应该可以解决您的问题。

当然,另一个解决方案是为此使用命名管道。但是,这取决于您的脚本实现命名管道或上述解决方法的复杂程度。

参考文献:


2

为了记录起见,即使答案和评论很好并且很有帮助,我还是结束了一些不同的实现(我在父进程中接收信号有一些限制,我在问题中没有提到)

基本上,我结束了这样的事情:

command <(subcomand 2>error_file && rm error_file) <(....) ...

然后,我检查错误文件。如果存在,我知道哪个子命令失败(error_file的内容可能很有用)。我本来想要的更加冗长而笨拙,但是比在一线Bash命令中创建命名管道要麻烦得多。


通常,最好的方法就是为您工作。特别感谢您回来并回答-自拍是我的最爱。
mikeserv

2

本示例说明如何与kill结合使用trap

#! /bin/bash
failure ()
{
  echo 'sub process failed' >&2
  exit 1
}
trap failure SIGUSR1
cat < <( false || kill -SIGUSR1 $$ )

但是kill不能将返回代码从您的子流程传递到父流程。


我喜欢接受的答案的这种变化
sehe

1

用与不支持POSIX shell的方式实现$PIPESTATUS/$pipestatus相似,可以通过管道传递命令来获得命令的退出状态:

unset -v false_status echo_status
{ code=$(
    exec 3>&1 >&4 4>&-
    cat 3>&- <(false 3>&-; echo >&3 "false_status=$?") \
             <(echo ok 3>&-; echo >&3 "echo_status=$?")
);} 4>&1
cat_status=$?
eval "$code"
printf '%s_code=%d\n' cat   "$cat_status" \
                      false "$false_status" \
                      echo  "$echo_status"

这使:

ok
cat_code=0
false_code=1
echo_code=0

或者,您可以pipefail像不支持它的shell一样手动使用和实现流程替换:

set -o pipefail
{
  false <&5 5<&- | {
    echo OK <&5 5<&- | {
      cat /dev/fd/3 /dev/fd/4
    } 4<&0 <&5 5<&-
  } 3<&0
} 5<&0
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.