想请教一个关于 Bash 管道符和 tee 的问题

46 天前
 jinqzzz

问题

v2 大佬比较多,想在这里请教各位大佬一个困惑了我多年的问题:如何在一行命令里,排序文件内容并用 tee 写到原来的文件中?

使用临时文件

想要将 foo.txt 文件中的文本排序后依然保存到 foo.txt 文件中,需要先写到一个临时文件,然后将临时文件重命名为 foo.txt 。这也是一个比较常见的方案。

sort foo.txt | tee tmp-foo.txt
mv tmp-foo.txt foo.txt

我一直以来都以为 tee 无法直接回写(不知道这个词用的对不对)到文件,如果直接 sort foo.txt | tee foo.txt,那么 foo.txt 的内容会是空的。

tee 有时可以直接回写文件

但是最近我发现并不是这样,有时是可以回写成功的。文件足够小时有很大概率可以直接回写,比如下图可以看到回写成功了两次。而稍微大点的文件就比较难

和朋友们的讨论

在 v2 上发帖提问之前,我和同事、朋友们讨论过这个问题,我们有了一点点进展。

我们认为,没有回写成功,可能是因为文件还没读完就去写入。因此可以让写入晚一点,比如加一个 sleep ,这样确实可以解决,也是目前为止唯一的解决方案。

sort foo.txt | { sleep 1; tee foo.txt; }

这样听起来很合理,但是我们还是不理解为什么有时没有读完

  1. 我用 strace 分析过回写成功和失败的日志,没有发现任何区别。
  2. sort 命令是在内存中排序,读取速度和硬件性能有关,但是内存频率高、性能好、读的快,就可以成功写入,这也太不稳定了。
  3. 这可能和 Bash 管道的实现有关吗?如果还没有执行完管道符前的命令,就去执行管道符后的命令了,听起来不太合理。
2103 次点击
所在节点    Linux
35 条回复
nuffin
46 天前
我其实觉得 sponge 不够 “管道”,因为它断流了。
sendi
46 天前
sort rpc.sh > >(tee rpc.sh)
可以使用进程替换来实现呢
sendi
46 天前
@sendi 也是类似于临时文件 但是 bash 在处理过程中有使用缓存或者临时存储
sendi
46 天前
https://www.yuque.com/wangsendi/hmeaaw/yhti79b6guut4yt5
可以参考 awk 的 结尾 1<>a 这样的模式 这样就不会截留了
jinqzzz
45 天前
@mohumohu 我的提问是不太准确,sort foo.txt | tee foo.txt 只是一种简化的场景,它代表了「如何在修改文件内容的同时,写入原文件」和「 | tee 的用法」 , 和 sort 没有太大关系。
想了想,我为什么都没想过 sort 有 -o ,因为更常见的场景是 cat foo.txt | xxx | xxx | tee foo.txt ,显然没人会去奢望前边有一个 -o 可以解决所有的问题...
jinqzzz
45 天前
@sendi 进程替换这种用法还没见过,学习了,确实挺好用。
但是我这里测试看有一个小问题,短时间内执行多次 sort foo.txt > >(tee foo.txt ) ,会有很低的概率把 foo 清空,如果用 for 批量执行,清空的概率就非常高了 for i in {01..20}; do echo $i; sort foo > >(tee foo ) ; done
jinqzzz
45 天前
@jinqzzz 还没写完就回车里,不过这种场景我现在还遇不到,就不太关心了。 1<> 是没有这个问题的,很好用
jinqzzz
45 天前
回车里 -> 回车了
jinliming2
45 天前
@jinqzzz sort 有个比较特殊的点是,它必须一次性把所有内容都读入才能开始输出,因为有可能最后一行的内容被排序到最前面。在输出之前,内容都是要读到内存里的,处理大文件要足够的内存。
所以可以用一些方法来延迟 tee 创建输出流的时间,确保 sort 已经读取所有内容。
如果是 cat xxx | tee xxx 这样的,cat 是支持流式处理的,也就是读多少输出多少,读取的内容可能比内存都要大,这种情况 sort 命令都肯定要失败的。这种就不建议延迟 tee 了,还是换个文件名来写,确保读取写入全部完成之后再做文件替换是比较稳妥的。
julyclyde
44 天前
@zhuisui 从技术上讲,用管道把俩进程连接起来,其打开顺序只能有一种吧
zhuisui
44 天前
@julyclyde 理论上 bash 对管道没有这样的声明,实际上自然也不能做这样的假定。
而且先开输入再开输出再连接它们在技术上是完全可行的。
zhuisui
44 天前
@jinqzzz 因为 process substitution 也可能用 pipe 实现,道理一样
https://www.gnu.org/software/bash/manual/html_node/Process-Substitution.html
julyclyde
44 天前
@zhuisui 先开输入再开输入似乎不能保证完整性吧?
zhuisui
44 天前
@julyclyde
先开 a 输出再开 b 输入把 a 接到 b ,就需要 pipe 先 buffer 一下 a 输出;反过来直接对接管道就行了,甚至不需要 buffer 。
你说呢
onnethy
40 天前
@aloxaf 牛逼

这是一个专为移动设备优化的页面(即为了让你能够在 Google 搜索结果里秒开这个页面),如果你希望参与 V2EX 社区的讨论,你可以继续到 V2EX 上打开本讨论主题的完整版本。

https://www.v2ex.com/t/1039828

V2EX 是创意工作者们的社区,是一个分享自己正在做的有趣事物、交流想法,可以遇见新朋友甚至新机会的地方。

V2EX is a community of developers, designers and creative people.

© 2021 V2EX