重定向到 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 按预期修改(其他文件仍然为空)。还有一些时候,它甚至对一个文件都不起作用。
- 你能告诉我怎样才能达到预期的结果吗?
- 第二个命令发生了什么?
读取文件的同时覆盖它是灾难的根源。但是 cmd $f > $f
bash 在 cmd
甚至 运行 之前清空 (= t运行cates) 文件。 cmd $f | tee $f
可能适用于短文件,因为 cmd
和 tee
运行 并行并且 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"
这是我要执行的不言自明的一行代码:
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 按预期修改(其他文件仍然为空)。还有一些时候,它甚至对一个文件都不起作用。
- 你能告诉我怎样才能达到预期的结果吗?
- 第二个命令发生了什么?
读取文件的同时覆盖它是灾难的根源。但是 cmd $f > $f
bash 在 cmd
甚至 运行 之前清空 (= t运行cates) 文件。 cmd $f | tee $f
可能适用于短文件,因为 cmd
和 tee
运行 并行并且 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"