【问题标题】:Why piping to the same file doesn't work on some platforms?为什么管道到同一个文件在某些​​平台上不起作用?
【发布时间】:2012-05-22 03:09:45
【问题描述】:

在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管道中是否有任何万无一失的方法来覆盖我不知道的文件。

【问题讨论】:

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


【解决方案1】:

使用下面的简单脚本,你可以让它像你想要的那样工作:

$ 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"

请注意,如果您不希望将更新后的文件发送到标准输出,则可以改用此方法

overwrite_file_no_output.sh

#!/usr/bin/env bash

OUT=$(cat -)

FILENAME="$*"

echo "$OUT" > "$FILENAME"

【讨论】:

    【解决方案2】:

    在管道中覆盖相同的文件不是建议,因为当你犯了错误时,你无法找回它(除非你有备份或者它是受版本控制的)。

    发生这种情况是因为管道中的输入和输出是自动缓冲的(给人的印象是它可以工作),但实际上它是并行运行的。不同的平台可能会以不同的方式缓冲输出(基于设置),因此在某些平台上您最终会得到空文件(因为文件将在开始时创建),在其他平台上会以半成品文件结束。

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

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

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

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

    • 避免使用 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
      

    【讨论】:

      【解决方案3】:

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

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

      【讨论】:

        【解决方案4】:

        这里有四个要点:

        1. cat 的无用使用。”不要那样做。
        2. 您实际上并没有使用 sort 对任何内容进行排序。不要那样做。
        3. 您的管道并没有说出您认为的那样。不要那样做。
        4. 您试图在读取文件时就地覆盖文件。不要那样做。

        您获得不一致行为的原因之一是您正在通过管道传输到具有重定向的进程,而不是重定向整个管道的输出。区别很微妙,但很重要。

        你想要的是用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之前不会覆盖垃圾,因此您将获得预期的结果。

        【讨论】:

        • 感谢您周到的回答!我改变了输入只是为了让我的问题更清楚。需要对原始输入进行排序。但你是对的。在这个例子中 sort 没有做任何事情
        • sponge 就像cat
        • @nobar 他们不一样。请参阅 moreutils 包中的 sponge(1) man page
        • @CodeGnome:对不起,我不打算发布那个。这只是我从未完成的句子的开始。相反,我在here 上写了一个关于sponge 的使用说明。
        【解决方案5】:

        一般来说,这可能会中断。管道中的进程都是并行启动的,因此行尾的&gt; junk 通常会在流水线头部的进程完成(甚至开始)读取之前截断您的输入文件。

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

        【讨论】:

          猜你喜欢
          • 1970-01-01
          • 2014-12-19
          • 2010-10-02
          • 2021-12-22
          • 2016-01-25
          • 1970-01-01
          • 1970-01-01
          • 2015-09-10
          • 1970-01-01
          相关资源
          最近更新 更多