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

时间:2010-01-18 23:07:20

标签: bash shell pipe

我经常遇到shell编程任务,我遇到这种模式:

cat file | some_script > file

这是不安全的 - 在some_script开始写入之前,cat可能没有读取整个文件。我真的不想把结果写到一个临时文件中(它很慢,而且我不希望增加复杂性来考虑一个独特的新名称)。

也许,有一个标准的shell命令会缓冲整个流,直到达到EOF为止?类似的东西:

cat file | bufferUntilEOF | script > file

想法?

9 个答案:

答案 0 :(得分:4)

您正在寻找sponge

答案 1 :(得分:4)

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

答案 2 :(得分:3)

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

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

如果命令失败,这将保留临时文件。如果要清除错误,可以将其更改为:

some_script < file > smscrpt.$$ && mv smscrpt.$$ file || rm smscrpt.$$
顺便说一下,我摆脱了对猫的不良使用,并用输入重定向取而代之。

答案 3 :(得分:2)

使用mktemp(1)tempfile(1)可以省去必须考虑唯一文件名的费用。

答案 4 :(得分:1)

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

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

答案 5 :(得分:1)

我认为最好的方法是使用临时文件。但是,如果您想要另一种方法,可以使用awk之类的东西在应用程序开始接收输入之前将输入缓冲到内存中。以下脚本将在将lines数组开始输出到管道中的下一个使用者之前将所有输入缓冲到{ lines[NR] = $0; } END { for (line_no=1; line_no<=NR; ++line_no) { print lines[line_no]; } } 数组中。

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

如果您愿意,可以将其折叠为单行:

{{1}}

尽管如此,我仍然建议使用临时文件进行输出,然后用它覆盖原始文件。

答案 6 :(得分:1)

回复the OP's question above关于在没有外部依赖关系的情况下使用<import />以及在@D.Shawley's answer上构建时,您可以使用仅仅依赖于sponge的海绵效果,在Unix或类Unix系统上并不罕见:

gawk

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;}' 的检查是截断输入文件。

要在shell脚本中使用它,请将NR>0更改为-voutfn=foo或shell用于文件名参数的任何语法。例如:

-voutfn="$1"

请注意,与真实#!/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的大小。如果需要,sponge实际上会在临时文件中缓冲。

答案 7 :(得分:1)

另一种选择是将文件读入变量:

file_contents=$(cat file)
echo "$file_contents" | script1 | script2 > file

答案 8 :(得分:0)

我认为您需要使用mktemp。这样的事情会起作用:

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