Bash中无需显式临时文件实现文件写入自身的技巧
注意:我专门在寻找代码奇技淫巧方案,而非替代解决方案。我清楚awk、sed等工具可以很好地完成这类内联编辑操作。
问题复现命令:
$ echo '1' > test $ cat test > test $ cat test $
直接执行cat test > test会得到空文件,核心原因是shell在启动cat命令前,就会先处理> test重定向,以写模式打开文件并截断所有内容,等cat开始读test的时候,文件已经是空的了。
需求是找到方法让上述第二条命令执行后,文件仍然保留原始内容1,要求无需显式使用二级文件(后台隐式使用二级文件/内核缓冲是允许的),不需要常规的sed -i、awk -i inplace这类通用替代方案。
方案1:利用inode引用计数机制(无额外依赖,100%可靠)
这个方案利用POSIX文件系统的核心特性:文件被进程打开持有文件描述符时,即使从目录中被删除(unlink),inode对应的数据块也不会被回收,直到所有指向该inode的文件描述符关闭才会真正清除数据。
最简洁的原生shell写法:
( rm test; cat > test ) < test
执行逻辑无任何竞态:
- shell启动子shell时,首先处理末尾的
< test重定向,以读模式打开原test文件,绑定到子shell的标准输入,此时原文件的inode已经被stdin的文件描述符持有 - 子shell内先执行
rm test,删除test对应的目录项,此时目录里看不到原文件,但原文件的inode和数据因为stdin的引用仍然完整保留 - 接着执行
cat > test:cat默认从标准输入读取原文件的全部内容,同时> test会创建一个和原文件完全无关的新同名文件(分配新inode),把读到的原内容写入新文件 - 命令执行结束子shell退出,stdin持有的旧文件描述符自动关闭,旧inode因为引用计数归0被系统回收。全程没有显式创建任何临时文件,所有内核层面的操作对用户完全透明,不管文件多大都能稳定工作。
提示:这个技巧仅做玩法展示,如果中途进程被异常终止,原文件已经被删除、新文件未写完会导致数据丢失,重要数据操作请用正规的原地编辑工具。
方案2:用sponge自动缓冲(写法最简洁)
sponge是moreutils工具集里的专用小工具,设计初衷就是解决这类"读文件同时写回同一个文件"的场景,它会自动把所有输入内容读入缓冲(内存不足时自动调用后台临时文件,对用户无感知),等输入完全读取完毕后再写入目标文件,不会提前截断文件。
命令写法:
cat test | sponge test
执行逻辑:
- 管道左侧的
cat test启动读取原文件内容,通过管道传给sponge sponge持续接收所有输入存入缓冲,期间不会操作目标文件- 等输入流完全结束(原文件内容全部读完),
sponge才打开test文件写入缓冲的内容,全程不需要手动指定临时文件名。
网上流传的cat <(cat test) >test类写法属于典型的竞态漏洞:在文件非常小的时候可能碰巧成功,但本质是赌进程替换里的cat test能在shell截断文件前读完所有内容,处理大文件时几乎必然出现内容丢失,不要在任何真实场景使用。
内容的提问来源于stack exchange,提问作者Roel Van de Paar

