读写文件:tee命令


10

众所周知,这样的命令:

cat filename | some_sed_command >filename

删除文件文件名,因为在命令之前执行输出重定向会导致文件名被截断。

可以通过以下方式解决此问题:

cat file | some_sed_command | tee file >/dev/null

但是我不确定这在任何情况下都行不通:如果文件(以及sed命令的结果)很大,会发生什么?操作系统如何避免覆盖仍未读取的某些内容?我看到还有一个海绵命令无论如何都应该起作用:它比tee更“安全”吗?


您的主要目标是什么?(以简单的方式)
Sergiy Kolodyazhnyy '16

@Serg只是了解事情是如何运作的... kos编写的答案澄清了这一点
VeryHardCoder '16

Answers:


10

可以通过以下方式解决此问题:

cat file | some_sed_command | tee file >/dev/null

没有

机会file将被截断掉,但不能保证cat file | some_sed_command | tee file >/dev/null不会截断file

这完全取决于首先处理哪个命令,而不是期望的那样,管道中的命令不是从左到右处理的。不能保证首先选择哪个命令,因此最好将其视为随机选择的,而绝不要依赖shell不选择有问题的命令。

由于在三个命令之间首先选择有问题的命令的几率比在两个命令之间首先选择有问题的命令的几率低,因此file被截断的可能性较小,但仍然会发生

script.sh

#!/bin/bash
for ((i=0; i<100; i++)); do
    cat >file <<-EOF
    foo
    bar
    EOF
    cat file |
        sed 's/bar/baz/' |
        tee file >/dev/null
    [ -s file ] &&
        echo 'Not truncated' ||
        echo 'Truncated'
done |
    sort |
    uniq -c
rm file
% bash script.sh
 93 Not truncated
  7 Truncated
% bash script.sh
 98 Not truncated
  2 Truncated
% bash script.sh
100 Not truncated

因此,切勿使用cat file | some_sed_command | tee file >/dev/nullsponge按照Oli建议使用。

作为替代,对于更紧凑的环境和/或相对较小的文件,可以在运行任何命令之前使用here字符串和命令替换来读取文件:

$ cat file
foo
bar
$ for ((i=0; i<100; i++)); do <<<"$(<file)" sed 's/bar/baz/' >file; done
$ cat file
foo
baz

9

对于sed具体情况,你可以使用它的-i就地说法。它只是保存回到打开的文件中,例如:

sed -i 's/ /-/g' filename

如果您想做更多的事情,假设您要做的不只是sed,是的,您可以使用sponge(从moreutils包中)缓冲整个操作,这将在写入文件之前“吸收”所有的stdin。就像tee但是功能较少。不过,对于基本用法,它几乎是一个替代品:

cat file | some_sed_command | sponge file >/dev/null

这样安全吗?绝对是 它可能有一定的局限性,因此,如果您做的事情非常庞大(并且无法使用sed进行就地编辑),则可能要对第二个文件进行编辑,然后mv将该文件恢复为原始文件名。那应该是原子的(因此取决于这些文件的任何内容如果需要不断访问都不会中断)。


0

您可以在Ex模式下使用Vim:

ex -sc '%!some_sed_command' -cx filename
  1. % 选择所有行

  2. ! 运行命令

  3. x 保存并退出


0

哦,但这sponge不是唯一的选择。您不必moreutils为了使它正常工作而获取。只要满足以下两个要求,任何机制都将起作用:

  1. 它接受输出文件的名称作为参数。
  2. 仅在处理完所有输入后,才创建输出文件。

您会看到,OP所指的一个众所周知的问题是,即使在开始执行管道中的命令之前,shell也会创建管道工作所需的所有文件,因此实际上是截断了shell在任何命令甚至没有机会开始执行之前,输出文件(不幸的是,它也是输入文件)。

tee即使满足第一个要求,该命令也不起作用,因为它不满足第二个要求:它将始终在启动时立即创建输出文件,因此从本质上讲,它与直接在输出文件中创建管道一样糟糕。(实际上更糟,因为它的使用会在输出文件被截断之前引入不确定的随机延迟,因此您可能会认为它起作用,而实际上却没有。)

因此,为了解决此问题,我们需要的是一条命令,该命令将在生成任何输出之前缓冲其所有输入,并且能够接受输出文件名作为参数,因此我们不必将其输出通过管道传递给输出文件。一个这样的命令是shuf。因此,以下将完成相同的sponge操作:

    shuf --output=file --random-source=/dev/zero 

--random-source=/dev/zero部件欺骗性地完成shuf了它的工作而根本不进行任何改组,因此它将缓冲您的输入而不会对其进行更改。

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.