我编写了一个等待标准输入数据的程序,将其写入 临时位置,并在输入结束时将其移动到用户指定的位置。
通过这个,我想启用像这样的管道
# Does not work; truncates myfile
cat myfile | some_filter > myfile
# shall enable pseudo-inplace modification
cat myfile | some_filter | my_program myfile
但无论上游管道是否成功,我的程序当前都会写入该文件。如果上游遇到错误,我想避免数据丢失,因此如果管道上游的某个程序发生错误,则中止程序。
截至目前,我用bash编写了程序,但并不局限于此。
如何在我的程序中查看?
编辑:更具体一点:我想让我的用户无需处理创建临时文件,尽可能检查中间程序的成功与否。我希望用户能够在某个文件上调用某个程序(例如,将列添加到文本文件,排序,过滤等),并将结果写回同一个文件,但前提是中间程序返回成功。
答案 0 :(得分:1)
重定向,例如> file
由shell处理,它在调用命令之前打开文件进行写入。解决方法是 - 正如@dood在评论中所建议的,使用临时文件:
tmp=$(mktemp)
a file | b | c > "$tmp"; && mv "$tmp" file
现在由mktemp
创建的文件用于重定向。然后将文件移至file
。
但是,这只会检查管道中的最后一个命令,如果成功则移动。
在bash中有一个名为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
,...因此,如果管道中的所有命令都成功退出,则基本匹配。
答案 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将永远不会启动。