为什么管道到同一个文件在某些​​平台上不起作用?

时间:2012-05-14 15:44:28

标签: bash pipeline overwrite io-redirection in-place

在cygwin中,以下代码可以正常运行

$ cat junk
bat
bat
bat

$ cat junk | sort -k1,1 |tr 'b' 'z' > junk

$ cat junk
zat
zat
zat

但是在linux shell(GNU / Linux)中,似乎覆盖不起作用

[41] othershell: cat junk
cat
cat
cat
[42] othershell: cat junk |sort -k1,1 |tr 'c' 'z'
zat
zat
zat
[43] othershell: cat junk |sort -k1,1 |tr 'c' 'z' > junk
[44] othershell: cat junk

两个环境都运行BASH。

我问这个是因为有时在我进行文本操作之后,由于这个警告,我被迫制作tmp文件。但我知道在Perl中,你可以在执行某些操作/操作后给出“i”标志来覆盖原始文件。我只是想问一下unix管道中是否有任何万无一失的方法来覆盖我不知道的文件。

5 个答案:

答案 0 :(得分:11)

这里有四个要点:

  1. “无用地使用 cat 。”不要那样做。
  2. 您实际上并没有使用 sort 对任何内容进行排序。不要那样做。
  3. 您的管道没有说出您的想法。不要那样做。
  4. 您正在尝试在读取文件时对其进行覆盖。不要那样做。
  5. 您获得不一致行为的原因之一是您正在使用具有重定向的进程,而不是重定向整个管道的输出。差异很微妙,但很重要。

    您想要的是使用Command Grouping创建复合命令,以便您可以重定向整个管道的输入和输出。在您的情况下,这应该正常工作:

    { sort -k1,1 | tr 'c' 'z'; } < junk > sorted_junk
    

    请注意,如果没有任何要排序的内容,您也可以跳过 sort 命令。然后您的命令可以在不需要命令分组的情况下运行:

    tr 'c' 'z' < junk > sorted_junk
    

    保持重定向和管道尽可能简单。它使调试脚本变得更加容易。

    但是,如果由于某种原因仍想滥用管道,可以使用 moreutils 包中的 sponge 实用程序。该手册页说:

      

    海绵读取标准输入并将其写入指定的输入   文件。与shell重定向不同,海绵之前会吸收所有输入   打开输出文件。这允许收缩读取的管道   来自并写入同一文件。

    因此,您的原始命令行可以像这样重写:

    cat junk | sort -k1,1 | tr 'c' 'z' | sponge junk
    

    并且由于在海绵从管道接收到EOF之前不会覆盖垃圾,您将获得您期望的结果。

答案 1 :(得分:6)

总的来说,这可能会破裂。管道中的进程都是并行启动的,因此行末的> junk通常会在流水线开头的流程完成(甚至启动)读取之前截断输入文件

即使Cygwin下的bash让你侥幸逃脱,你也不应该依赖它。一般的解决方案是重定向到临时文件,然后在管道完成时重命名它。

答案 2 :(得分:3)

您想要编辑该文件,您只需使用编辑器。

ex junk << EOF
%!(sort -k1,1 |tr 'b' 'z')
x
EOF

答案 3 :(得分:0)

覆盖管道中的同一个文件不是建议,因为当你犯错误时你无法取回它(除非你备份或它是版本控制不足)。 / p>

这种情况会发生,因为管道中的输入和输出会自动缓冲(这会给你一个印象),但实际上它并行运行。不同的平台可以以不同的方式缓冲输出(基于设置),因此有些人最终会得到空文件(因为文件将在开始时创建),而另一些则是半完成文件。

解决方案是在文件仅在遇到具有完全缓冲和处理输入的EOF时被覆盖时使用某种方法。

这可以通过以下方式实现:

  • 使用在打开输出文件之前可以吸收所有输入的实用程序。

    这可以通过sponge完成(与unbuffer包中的expect相反)。

  • 避免使用I / O重定向语法(可以在启动命令之前创建空文件)。

    例如使用tee(缓冲其标准流),例如:

    cat junk | sort | tee junk
    

    这只适用于sort,因为它希望所有输入都能处理排序。因此,如果您的命令没有使用sort,请添加一个。

    可以使用的另一个工具是stdbuf,它可以修改标准流的缓冲操作,您可以在其中指定缓冲区大小。

  • 使用可以就地编辑文件的文本处理器(例如sedex)。

    示例:

    $ ex -s +'%!sort -k1' -cxa myfile.txt
    $ sed -i '' s/foo/bar/g myfile.txt
    

答案 4 :(得分:0)

使用以下简单脚本,可以使其按需运行:

$ cat junk | sort -k1,1 |tr 'b' 'z' | overwrite_file.sh junk

overwrite_file.sh

#!/usr/bin/env bash

OUT=$(cat -)

FILENAME="$*"

echo "$OUT" | tee "$FILENAME"

请注意,如果您不希望将更新的文件发送到stdout,则可以改用这种方法

overwrite_file_no_output.sh

#!/usr/bin/env bash

OUT=$(cat -)

FILENAME="$*"

echo "$OUT" > "$FILENAME"