在管道中,下游程序如何在输入结束后获取上游程序的退出代码?

时间:2016-06-08 11:32:16

标签: bash pipe sh exit-code

我编写了一个等待标准输入数据的程序,将其写入 临时位置,并在输入结束时将其移动到用户指定的位置。

通过这个,我想启用像这样的管道

# Does not work; truncates myfile
cat myfile | some_filter > myfile 
# shall enable pseudo-inplace modification
cat myfile | some_filter | my_program myfile 

但无论上游管道是否成功,我的程序当前都会写入该文件。如果上游遇到错误,我想避免数据丢失,因此如果管道上游的某个程序发生错误,则中止程序。

截至目前,我用bash编写了程序,但并不局限于此。

如何在我的程序中查看?

编辑:更具体一点:我想让我的用户无需处理创建临时文件,尽可能检查中间程序的成功与否。我希望用户能够在某个文件上调用某个程序(例如,将列添加到文本文件,排序,过滤等),并将结果写回同一个文件,但前提是中间程序返回成功。

2 个答案:

答案 0 :(得分:1)

重定向,例如> file由shell处理,它在调用命令之前打开文件进行写入。解决方法是 - 正如@dood在评论中所建议的,使用临时文件:

tmp=$(mktemp)
a file | b | c > "$tmp"; && mv "$tmp" file

现在由mktemp创建的文件用于重定向。然后将文件移至file

但是,这只会检查管道中的最后一个命令,如果成功则移动。

中有一个名为PIPESTATUS的变量,其中包含管道的所有退出状态,例如:

% ls | cat | cat
% echo "${PIPESTATUS[@]}"
0 0 0

失败:

% ls | cat | false
% echo "${PIPESTATUS[@]}"
0 141 1

您可以使用它来检查管道中的所有命令是否已成功退出:

% ls | cat | cat
% [[ "${PIPESTATUS[@]}" =~ ^0( 0)*$ ]] && echo "good"
good

以你的榜样:

tmp=$(mktemp)
some_filter < myfile > "$tmp"
[[ "${PIPESTATUS[@]}" =~ ^0( 0)*$ ]] && mv "$tmp" "myfile"

正则表达式^0( 0)*$匹配00 00 0 0,...因此,如果管道中的所有命令都成功退出,则基本匹配。

答案 1 :(得分:1)

管道中运行命令的重点是它们同时执行。当您运行cmd1 | cmd2 | cmd3时,cmd3很可能在cmd1完成之前开始执行。因此,除非您可以调用星际迷航风格的时间异常,否则无法在cmd3启动之前确定cmd1的退出状态。如果在开始cmd3之前需要等到cmd1完成,则无法将它们放在同一个管道中。嗯,这不是真的,你可以做像黑客那样的黑客攻击。

mkfifo /tmp/foo; { cmd1; echo > /tmp/foo; } | cmd2 | { cat /tmp/foo; cmd3; }

但你为什么要这样? (请注意,即使在这种情况下,管道的最后部分中的命令也会在cmd1完成之前执行,但cat会阻止cmd3的执行被延迟。)

另请注意,无论您尝试什么,都存在潜在的死锁。如果管道已满,cmd1和cmd2将在写入时无限期阻塞,cmd3将永远不会启动。