重定向到 Bash 循环内的文件 + 命令随机工作

Redirection to file inside Bash loop + command works randomly

这是我要执行的不言自明的一行代码:

for f in *; do awk '{sub(FILENAME, FILENAME".")1}' $f > $f; done

此命令无法正常工作。输出文件都是空的。我已经在互联网上搜索了发生这种情况的原因,结果发现 Bash 中的循环被认为是一个单一的命令,因此在 "done"

之后流重定向应该在它之外

我接着试了这个,结果更让人吃惊:

for f in *; do awk '{sub(FILENAME, FILENAME".")1}' $f | tee $f; done

所以现在也不起作用,除了有时它对目录中的一个文件起作用,不一样。我复制目录中文件的新副本(我已经在其他地方备份),我 运行 一行,文件 B 按预期修改(其他变为空)。然后我重新复制新副本,重新 运行 命令,然后文件 C 按预期修改(其他文件仍然为空)。还有一些时候,它甚至对一个文件都不起作用。

  1. 你能告诉我怎样才能达到预期的结果吗?
  2. 第二个命令发生了什么?

读取文件的同时覆盖它是灾难的根源。但是 cmd $f > $f bash 在 cmd 甚至 运行 之前清空 (= t运行cates) 文件。 cmd $f | tee $f 可能适用于短文件,因为 cmdtee 运行 并行并且 cmd 的输出被缓冲。如果幸运的话,您的系统会在 tee 的 t运行cate 操作之前执行 cmd 的读取操作。文件越大,在 tee t运行 对其进行分类之前读取 所有 数据的机会就越小。

如果您想亲自查看 cmd 的读取操作和 tee 的 t运行cate 操作之间的竞争条件,请查看

head -c1M /dev/zero > f; LC_ALL=C strace -f -e execve,openat,read,write bash -c 'cat f | tee f' >/dev/null; wc -c f

我的 tee GNU coreutils 8.32 t运行 实现通过调用 openat(… "f" … O_TRUNC …) 对文件进行分类。该操作成功后,cat 的下一个 read 将 return = 0,表示文件结束。

对于您的情况,存在三种可能的解决方案:

  • 使用一个你之后重命名的临时文件
    awk ... "$f" > "$f.tmp"; mv "$f.tmp" "$f"
  • 使用 GNU awk 的就地选项
    gawk -i inplace ... "$f"
  • 使用来自 GNU moreutils 的海绵
    awk ... "$f" | sponge "$f"