2010-01-18 52 views
7

我经常有shell编程任务,在那里我遇到了这个模式:使用管道时修改文件的最佳方法是什么?

cat file | some_script > file 

这是不安全的 - 猫可能不会在整个文件之前some_script开始写它已经阅读。我真的不想把结果写到一个临时文件中(它很慢,而且我也不想再考虑一个独特的新名字)。

也许,有一个标准的shell命令会缓冲整个流直到EOF达到?例如:

cat file | bufferUntilEOF | script > file 

想法?

+0

恩,xargs应该这样做,对不对? – 2010-01-18 23:10:42

+0

我不这么认为。那么,也许它确实如此,但是它的文档说它解决的问题是处理命令参数限制被拒绝的情况。它并没有说它在打开stdout之前缓冲了所有的stdin。 – user48956 2010-01-18 23:16:38

+0

我认为有些选项可以处理缓冲大小的xargs。 – 2010-01-18 23:20:46

回答

1

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

它几乎打败了管道缓冲他们的目的。

+0

嗯,也许。听起来像是一个宗教论证。我知道所有这些文件很容易放在主内存的一小部分内容中(我的shell脚本将在一个非常大的SVN仓库中的每个源文件上运行)。 临时文件将使其运行两次(必要时至少在Cygwin中)。 – user48956 2010-01-18 23:36:24

+0

这可能是。如果您的代码将始终按照您期望的方式使用,那么明智的权衡是合理的... – 2010-01-18 23:44:06

+0

@stuartreynolds:使用临时文件不会使其运行速度变慢,除了可能可以忽略将文件重命名为原始名称的时间不变。 – Juliano 2010-01-18 23:45:13

3

您正在寻找sponge

+0

这看起来像一个很好的解决方案,但我不想要求我的脚本的所有用户都安装其他依赖项(或编译任何代码)。 - 没有使用标准实用程序或内置shell功能的替代方法吗? – user48956 2010-01-18 23:33:26

+1

我不推荐海绵。如果管道中的任何命令(除了海绵)都失败(例如,由于语法错误,无效参数等),它将擦除文件,并且在没有原始文件和目标文件的情况下结束。 – Juliano 2010-01-18 23:37:54

+0

/tmp可以安装在内存中(至少在Linux下)。在这种情况下,我希望这可能非常快。不过在Cygwin中并不确定/ tmp。 Cygwin是否在内存中保存这些内容? – user48956 2010-01-19 00:51:51

4

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

2

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

+0

投票了,优秀的工具。 – Anders 2010-01-19 00:24:33

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 

有了这一切,我还是会建议使用临时文件的输出,然后覆盖与原文件它。

2

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

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

如果命令失败,这将保留临时文件。如果你想清理的错误,您可以更改到:

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

BTW,我摆脱了贫穷使用猫,并与输入重定向取而代之。

+0

谢谢 - 这是一个很好的窍门。如果some_script失败,你会泄漏一个文件。需要处理的情况: “(some_script < file > smscrpt。$$ && mv smscrpt。$$ file)|| \ rm -f smscrpt。$$” 仍然会喜欢这样的:“(some_script 文件“,因为(i)它的方式更容易阅读,(ii)我不必记得进行错误检查(iii)我相信它会在Cygwin下运行得更快,因为Godawful慢速文件访问。 – user48956 2010-01-19 00:48:37

+2

@stuartreynolds--有人发布了关于海绵的问题,你拒绝了,因为它不是标准的。没有什么标准可以做你想要的东西。 – 2010-01-19 01:19:42

+1

@klatchko - 我认为像海绵*是我寻找的答案(与我提到的警告 - 它不是很容易广泛使用它)。国际海事组织,如果真的没有什么做什么海绵做的,*和*海绵的功能是shell脚本的基础(缓冲以避免文件损坏听起来对我来说非常重要),那么可能它应该是bash的一部分,或者标准的GNU工具集(在这种情况下,我希望有人会指出为什么我们不需要海绵......任何人?)。我真的*必须做一个临时文件来做到这一点? – user48956 2010-01-19 02:27:15

1

针对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;}' 

需要注意的是,不像真正的sponge,这可能仅限于RAM的大小。如果需要,sponge实际上会缓存在临时文件中。

相关问题