在测试对程序重复执行的输出求和的脚本时,我遇到了一种我不理解的行为。要重现它,请创建文本文件out
,它表示我的程序的输出,以及sum
,该文件保存先前执行时返回的值的总和,并且以 的副本开始out
,
cat > out << EOF
2 20
5 50
EOF
cp out sum
跑步时发生奇怪的事情
paste out sum | awk '{$1 += $3; $2 += $4; NF = 2; print}' | tee sum
多次(可能需要 15-20 次)。每次运行时,该命令都应将sum
相应值 in中的值相加out
,并将结果写回到sum
.我得到的是它工作了不可预测的次数,然后sum
恢复到
2 20
5 50
我后来了解到我无法将输出重定向或发球到我正在处理的同一文件并使用临时文件解决了问题,但这种行为仍然让我感到困惑:
为什么可以
… | tee sum
工作(即使只进行有限次数的迭代),而… > sum
从不覆盖sum
?为什么它不能按预期的次数工作?
答案1
这,
paste out sum | awk ... | tee sum
有竞争条件。paste
打开sum
以读取它,tee
打开它以进行写入,并截断它。 shell 几乎同时启动,因此取决于哪个 shell 首先打开文件。
当然,在实践中,shell 必须以某种特定的顺序一次启动一个实用程序。它可能是从左到右执行的,因此paste
可能有更好的机会先执行,但这是一个实现细节,无论如何,操作系统调度程序决定什么时候运行。
如果paste
先走,它会打开数据仍然完整的文件,并且可能也有足够的时间来读取数据。如果在读取tee
文件之前打开该文件,则会看到一个空文件。paste
paste
这里,
paste out sum | awk ... > sum
shell 打开sum
以进行写入,并将其截断。它可能与启动并行执行paste
,但由于截断sum
不涉及启动另一个实用程序,因此它可能首先发生。 (我不太确定是否有关于处理重定向和在这样的管道中启动命令的顺序的规则,但我不会指望它。)
有一个工具可以sponge
解决这个问题(还有十几个关于它的问题)。它收集所获得的输入,并仅在输入关闭后才将其写入。这应该sum
始终正确更新:
paste out sum | awk ... | sponge sum