代码之家  ›  专栏  ›  技术社区  ›  user48956

使用管道时修改文件的最佳方法?

  •  7
  • user48956  · 技术社区  · 15 年前

    我经常在shell编程任务中遇到以下模式:

    cat file | some_script > file
    

    这是不安全的-在某些脚本开始写入之前,cat可能没有读取整个文件。我真的不想把结果写到一个临时文件中(它很慢,我也不想再考虑一个独特的新名字的复杂性)。

    也许,有一个标准的shell命令将缓冲整个流,直到到达eof?类似于:

    cat file | bufferUntilEOF | script > file
    

    思想?

    7 回复  |  直到 15 年前
        1
  •  5
  •   Juliano    15 年前

    在这里使用临时文件是正确的解决方案。当您使用像“>”这样的重定向时,它由shell处理,无论您的管道中有多少命令,shell都可以在执行任何命令之前(在管道设置期间)自由删除和覆盖输出文件。

        2
  •  4
  •   chazomaticus    15 年前

    你在找 sponge .

        3
  •  3
  •   PRouleau    7 年前

    像许多其他人一样,我喜欢使用临时文件。我使用shell进程id作为临时名称的一部分,这样如果脚本的多个副本同时运行,它们就不会发生冲突。最后,我只在脚本成功时覆盖原始文件(使用布尔运算符短路-它有点密集,但对于简单的命令行来说非常好)。把这些放在一起,看起来像:

    some_script < file > smscrpt.$$ && mv smscrpt.$$ file
    

    如果命令失败,这将留下临时文件。如果你想清理错误,你可以把它改成:

    some_script < file > smscrpt.$$ && mv smscrpt.$$ file || rm smscrpt.$$
    

    顺便说一句,我去掉了cat的糟糕用法,用输入重定向代替了它。

        4
  •  2
  •   Dennis Williamson    15 年前

    使用 mktemp(1) tempfile(1) 节省了您必须考虑唯一文件名的费用。

        5
  •  1
  •   John Weldon user3678248    15 年前

    使用临时文件比尝试缓冲管道中的数据要好。

    这几乎违背了管道缓冲它们的目的。

        6
  •  1
  •   Community CDub    8 年前

    回应 the OP's question above 关于使用 sponge 没有外部依赖性,并且建立在 @D.Shawley's answer 你可以对海绵产生依赖性。 gawk ,这在unix或类unix系统中并不少见:

    cat foo | gawk -voutfn=foo '{lines[NR]=$0;} END {if(NR>0){print lines[1]>outfn;} for(i=2;i<=NR;++i) print lines[i] >> outfn;}'
    

    支票 NR>0 是截断输入文件。

    要在shell脚本中使用它,请更改 -voutfn=foo -voutfn="$1" 或者shell用于文件名参数的任何语法。例如:

    #!/bin/bash
    cat "$1" | gawk -voutfn="$1" '{lines[NR]=$0;} END {if(NR>0){print lines[1]>outfn;} for(i=2;i<=NR;++i) print lines[i] >> outfn;}'
    

    注意,不像真的 海绵 ,这可能仅限于RAM的大小。 海绵 实际上,如果需要,可以在临时文件中进行缓冲。

        7
  •  1
  •   cxw    9 年前

    我认为最好的方法是使用临时文件。但是,如果你想要另一种方法,你可以使用类似的方法。 awk 在应用程序开始接收输入之前将输入缓冲到内存中。下面的脚本将缓冲 lines 数组,然后它开始将其输出到管道中的下一个使用者。

    { lines[NR] = $0; }
    END {
        for (line_no=1; line_no<=NR; ++line_no) {
            print lines[line_no];
        }
    }
    

    如果你愿意,你可以把它折叠成11个衬垫:

    cat file | awk '{lines[NR]=$0;} END {for(i=1;i<=NR;++i) print lines[i];}' > file
    

    尽管如此,我还是建议使用一个临时文件作为输出,然后用它覆盖原始文件。

        8
  •  0
  •   Rich    5 年前

    我想你需要用 mktemp . 类似这样的事情会奏效:

    FILE=example-input.txt
    TMP=`mktemp`
    some_script <"$FILE" >"$TMP"
    mv "$TMP" "$FILE"