Toothrot Asked: 2019-12-25 05:24:39 +0800 CST2019-12-25 05:24:39 +0800 CST 2019-12-25 05:24:39 +0800 CST 将管道弯曲回其原点 772 有人可能认为 echo foo >a cat a | rev >a 将离开a包含oof;但它是空的。 为什么? 否则将如何适用rev于a? pipe io-redirection 7 个回答 Voted Best Answer terdon 2019-12-25T05:53:51+08:002019-12-25T05:53:51+08:00 有一个应用程序!来自的sponge命令moreutils正是为此而设计的。如果您正在运行 Linux,它可能已经安装,如果没有,请在操作系统的存储库中搜索sponge或moreutils. 然后,你可以这样做: echo foo >a cat a | rev | sponge a 或者,避免UUoC: rev a | sponge a 这种行为的原因在于您的命令的运行顺序。这> a实际上是执行的第一件事并> file清空文件。例如: $ echo "foo" > file $ cat file foo $ > file $ cat file $ 所以,当你运行时cat a | rev >a,实际发生的> a是首先运行,清空文件,所以当cat a你执行时,文件已经是空的。这正是sponge写成的原因(来自man sponge,强调我的): 海绵读取标准输入并将其写入指定文件。 与 shell 重定向不同,海绵在写入输出文件之前会吸收其所有输入。这允许构建读取和写入同一文件的管道。 stolenmoment 2019-12-25T05:40:35+08:002019-12-25T05:40:35+08:00 输出截断很早就完成了,所以 cat 看到一个空文件。 第一个文件被构造为临时文件,或者 rev 的输出被定向到您然后重命名的临时文件。 Jasen 2019-12-26T02:27:27+08:002019-12-26T02:27:27+08:00 解决此问题的另一种方法是使用不会截断的书写方法 rev a | dd conv=notrunc of=a 这只有效,因为: rev 在产生输出之前读取内容,并且输出永远不会超过已经读取的量 新文件内容与原始文件大小相同或更大(在这种情况下大小相同) dd 打开要写入的文件而不截断它。 这种方法对于就地修改太大而无法保留临时副本的文件可能很有用。 ilkkachu 2019-12-26T12:51:21+08:002019-12-26T12:51:21+08:00 cat a | rev > a 为什么[a留空]? 在上面的管道中,shell 分叉了两个子进程,一个用于管道的两个部分中的每一个。然后这些子进程运行有问题的命令,首先处理任何重定向,然后调用其中一个exec*()函数来启动外部实用程序。子进程并行运行,它们之间没有时间保证。 执行一个进程不是很快,所以通常发生的是右侧的 shell 在cat有机会读取文件之前设法设置重定向。输出重定向> a会截断文件,因此cat没有可读取的内容,rev不接收任何数据,也不产生任何数据。即使您在左侧也使用了重定向cat < a | rev > a(acat 另一方面,这非常一致地打印a contains: foo在我的系统上: echo foo > a; cat < a | tee a > /dev/null ; echo "a contains: $(cat a)" 在这里,它tee会截断文件,所以这发生在 之后,exec()并且cat有更好的时间读取文件。但如果文件足够大,它可能会在读取过程中被截断。 我说可能并且可能在那里,因为如果操作系统决定以另一种方式安排进程,确实会发生完全相反的情况。 否则将如何适用rev于a? 通常的解决方案是使用临时文件: cat a | rev > b && mv b a 尽管通常存在可能覆盖现有文件的问题,除非您可以确定临时文件名可用。您可能应该使用mktemp: f=$(mktemp ./tmp.XXXXXX) cat a | rev > "$f" && mv "$f" a || rm "$f" 或者,您可以使用该sponge工具,它确保在打开输出文件之前读取它获得的所有输入(否则它就像cat): cat a | rev | sponge a 要不就 rev < a | sponge a sponge > a与原始命令不起作用的原因相同,这将是一个错误。 Sponge 来自moreutils,而不是标准工具。在管道到另一个命令之前完全缓冲命令输出中列出了它的一些替代方案? 一些实用程序可能自己实现类似的功能,例如sort -o outputfile只在完成后打开输出文件,请参阅Sort support sorting a file in-place, like `sed --in-place`? ikegami 2020-05-14T21:14:39+08:002020-05-14T21:14:39+08:00 >file创建一个新的空文件或截断现有文件。因此,文件中没有任何内容可供rev阅读。 正如其他答案所提到的,您可以使用sponge它。但sponge并不是所有人都可以使用。 以下是仅涉及外壳的通用解决方案: exec 3<file; rm file; rev <&3 >file; exec 3<&- 这将打开文件(作为 fd 3)并将其删除。更准确地说,这只会删除文件的目录条目,而不是文件本身。在删除指向它的所有硬链接并关闭它的所有句柄之前,不会删除该文件。 接下来,rev从“已删除文件”中运行读取。它的输出被发送到一个新文件。虽然这个新文件与原始文件同名,但它是一个不同的文件。因此,没有冲突。 最后,我们关闭原始文件的描述符,允许它被释放。 上述方法的问题是,如果出现问题,数据会丢失。这就是为什么人们可能更喜欢以下内容(它使用的磁盘空间不超过上述内容): ( rev file >file.new && mv file.new file ) || rm file.new Zombo 2019-12-28T10:16:34+08:002019-12-28T10:16:34+08:00 你可以在 Ex 模式下使用 Vim: ex -s -c '%!rev' -c x a.txt %选择所有行 !运行命令 x保存并关闭 tinnick 2020-05-14T21:53:58+08:002020-05-14T21:53:58+08:00 其他人已经解释了原因,但您不能这样做的原因基本相同: rev a >a 但是如何做到这一点: echo foo >a echo `cat a | rev` >a # or echo `rev a` >a
有一个应用程序!来自的
sponge
命令moreutils
正是为此而设计的。如果您正在运行 Linux,它可能已经安装,如果没有,请在操作系统的存储库中搜索sponge
或moreutils
. 然后,你可以这样做:或者,避免UUoC:
这种行为的原因在于您的命令的运行顺序。这
> a
实际上是执行的第一件事并> file
清空文件。例如:所以,当你运行时
cat a | rev >a
,实际发生的> a
是首先运行,清空文件,所以当cat a
你执行时,文件已经是空的。这正是sponge
写成的原因(来自man sponge
,强调我的):解决此问题的另一种方法是使用不会截断的书写方法
这只有效,因为:
rev 在产生输出之前读取内容,并且输出永远不会超过已经读取的量
新文件内容与原始文件大小相同或更大(在这种情况下大小相同)
这种方法对于就地修改太大而无法保留临时副本的文件可能很有用。
在上面的管道中,shell 分叉了两个子进程,一个用于管道的两个部分中的每一个。然后这些子进程运行有问题的命令,首先处理任何重定向,然后调用其中一个
exec*()
函数来启动外部实用程序。子进程并行运行,它们之间没有时间保证。执行一个进程不是很快,所以通常发生的是右侧的 shell 在
cat
有机会读取文件之前设法设置重定向。输出重定向> a
会截断文件,因此cat
没有可读取的内容,rev
不接收任何数据,也不产生任何数据。即使您在左侧也使用了重定向cat < a | rev > a
(a
cat
另一方面,这非常一致地打印
a contains: foo
在我的系统上:在这里,它
tee
会截断文件,所以这发生在 之后,exec()
并且cat
有更好的时间读取文件。但如果文件足够大,它可能会在读取过程中被截断。我说可能并且可能在那里,因为如果操作系统决定以另一种方式安排进程,确实会发生完全相反的情况。
通常的解决方案是使用临时文件:
尽管通常存在可能覆盖现有文件的问题,除非您可以确定临时文件名可用。您可能应该使用
mktemp
:或者,您可以使用该
sponge
工具,它确保在打开输出文件之前读取它获得的所有输入(否则它就像cat
):要不就
sponge > a
与原始命令不起作用的原因相同,这将是一个错误。Sponge 来自moreutils,而不是标准工具。在管道到另一个命令之前完全缓冲命令输出中列出了它的一些替代方案?
一些实用程序可能自己实现类似的功能,例如
sort -o outputfile
只在完成后打开输出文件,请参阅Sort support sorting a file in-place, like `sed --in-place`?>file
创建一个新的空文件或截断现有文件。因此,文件中没有任何内容可供rev
阅读。正如其他答案所提到的,您可以使用
sponge
它。但sponge
并不是所有人都可以使用。以下是仅涉及外壳的通用解决方案:
这将打开文件(作为 fd 3)并将其删除。更准确地说,这只会删除文件的目录条目,而不是文件本身。在删除指向它的所有硬链接并关闭它的所有句柄之前,不会删除该文件。
接下来,
rev
从“已删除文件”中运行读取。它的输出被发送到一个新文件。虽然这个新文件与原始文件同名,但它是一个不同的文件。因此,没有冲突。最后,我们关闭原始文件的描述符,允许它被释放。
上述方法的问题是,如果出现问题,数据会丢失。这就是为什么人们可能更喜欢以下内容(它使用的磁盘空间不超过上述内容):
你可以在 Ex 模式下使用 Vim:
%
选择所有行!
运行命令x
保存并关闭其他人已经解释了原因,但您不能这样做的原因基本相同:
但是如何做到这一点: