使用管道命令序列如下:
$ cat afile | somecommand | tee afile
afile
既读取(由 cat 读取),又写入(由 tee 写入)。问题是,假设一个文件至少有几兆字节(或大到操作系统无法完全缓冲),当 tee 开始写入文件时,cat 读取的文件中的后续字节会受到影响吗?
换句话说,在 cat 完成文件操作之前,tee 是否有可能覆盖该文件?
答案1
在流水线进程中,向正在同时读取的文件进行写入几乎总是一个可怕的错误。
这是因为写入操作与读取操作同时发生。通常会导致文件过早截断。过去,这让习惯于 DOS 管道的人感到惊讶,因为操作系统通过隐藏的临时文件(或道德等效物)序列化进程
解决方案主要涉及使用临时文件并在完成后重命名它们。
somecommand < infile | tee tempfile; mv tempfile infile
显然,这可能会引发其他问题。
如果您为某些实用程序(awk、perl 等)提供适当的命令行选项,它们会为您处理这个问题。
perl -i -e 'somecommands' infile ...
请注意,您遇到的问题与 无关cat
。在我的示例中,我避免了不必要地使用 cat,部分是为了明确这一点,部分是出于传统。
答案2
为了我自己的乐趣(以及对这个问题感兴趣的任何人),我做了一些实验。我afile
使用 /dev/urandom 生成了 100 MB,并使用同一个文件尝试了一些管道和重定向。
以下大多数命令都没有逻辑意义(因为“成功”结果应该保持文件不变),还应该注意,如果为输入和输出指定了不同的文件,则所有命令都会导致输入的完整副本。
以下是部分结果:
$ cat <afile >afile
定义:从 afile 中 cat stdin 并重定向到 afile。结果:afile 被截断(0 字节)
$ cat afile|cat >afile
定义:cat afile 并将 stdout 管道传输到另一个 cat,重定向到 afile。结果:afile 被截断(0 字节)
$ tee afile <afile >/dev/null
定义:从 afile 执行 tee stdin 并写入 afile(并将 stdout 重定向到 /dev/null)。结果:afile 被截断(0 字节)
$ cat afile|tee afile >/dev/null
定义:cat afile 并将 stdout 管道传输到 tee(并将 tee stdout 重定向到 /dev/null)。结果:afile 减小到 128 KB
最后一条记录最清楚地显示了问题:cat 只能缓冲 128 KB 并将其通过管道传输到 tee,然后文件就会消失。因此,如果您的文件很小,您可能很幸运,但最好留意答案并始终分开输入和输出文件。