【问题标题】:In a pipe, how can a program downstream get the exit code of a program upstream after end-of-input?在管道中,下游程序如何在输入结束后获取上游程序的退出代码?
【发布时间】:2016-06-08 11:32:16
【问题描述】:

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

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

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

但我的程序当前写入文件,无论上游管道是否成功。如果上游遇到错误,我想避免数据丢失,因此如果管道上游的程序之一抛出错误,则中止程序。

到目前为止,我用 bash 编写了程序,但并不局限于此。

如何在我的程序中检查这一点?

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

【问题讨论】:

  • 您可以尝试使用mktemp制作临时文件,写入临时文件,检查过程是否成功完成,然后从临时文件写入您的文件,删除临时文件。
  • 解释@dood所说的:tmpfile=$(mktemp); process1 | process2 > $tmpfile && mv $tmpfile myfile
  • 另外,cat myfile | some_filterUUOC。请改用some_filter < myfile
  • @dood 是的,这基本上就是我在程序中写入的内容。但是,我想对用户隐藏这一点,这就是为什么我问是否可以检查上游程序的错误代码从内部下游程序。
  • @anishsane 你说得对,我写这个只是为了更好地可视化管道结构。

标签: bash pipe sh exit-code


【解决方案1】:

重定向,例如> file 由shell 处理,它在调用命令之前打开文件进行写入。解决方法是 - 正如 cmets 中的 @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)*$ 匹配0, 0 0, 0 0 0, ... 所以如果管道中的所有命令都成功退出,则基本上匹配。

【讨论】:

  • 这正是我想要封装到程序中的内容:我的用户不必费心创建临时文件、读取管道状态并确保之后删除临时文件。
【解决方案2】:

在管道中运行命令的全部意义在于它们同时执行。当您运行cmd1 | cmd2 | cmd3 时,cmd3 很可能在cmd1 完成之前开始执行。因此,除非您可以调用星际迷航风格的时间异常,否则无法在 cmd3 启动之前确定 cmd1 的退出状态。如果您需要等到 cmd1 完成后再开始 cmd3,则不能将它们放在同一管道中。好吧,这不是真的,你可以做一些黑客之类的事情;

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

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

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

【讨论】:

  • 感谢您的解释。但是是否可能有以下时间进程:cmd1、cmd2 和 cmd3 都启动,cmd3 读取 cmd1|cmd2 的所有输出,直到传输结束。这意味着cmd2已经关闭了它的输出,那么cmd3应该能够在此时获得cmd2的退出状态?
  • 当然:cmd1 | cmd2 &gt; /tmp/foo &amp;&amp; cmd3 &lt; /tmp/foo/tmp 放在 ram 磁盘上,如果你想确保你永远不会碰到磁盘。您可以尝试使用命名管道等,但总会有潜在的死锁,而常规文件是避免这些问题的最佳方法。
猜你喜欢
  • 1970-01-01
  • 2015-07-28
  • 1970-01-01
  • 2012-03-11
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2018-12-04
  • 1970-01-01
相关资源
最近更新 更多