我想在任何现有的#include之前,用一个额外的include指令更新大量的c++源文件。对于这类任务,我通常使用带有sed的小型bash脚本来重写文件。

我如何得到sed替换一个文件中字符串的第一次出现,而不是替换每一次出现?

如果我用

sed s/#include/#include "newfile.h"\n#include/

它替换所有#includes。

我们也欢迎为达到同样目的而提出的其他建议。


当前回答

sed脚本只将“Apple”第一次出现的位置替换为“Banana”

例子

     Input:      Output:

     Apple       Banana
     Apple       Apple
     Orange      Orange
     Apple       Apple

这是一个简单的脚本:编辑注:只适用于GNU sed。

sed '0,/Apple/{s/Apple/Banana/}' input_filename

前两个参数0和/Apple/是范围说明符。s/Apple/Banana/是在这个范围内执行的。因此,在这种情况下,“在Apple的开始(0)到第一个实例的范围内,将Apple替换为Banana。只有第一代苹果会被取代。

Background: In traditional sed the range specifier is also "begin here" and "end here" (inclusive). However the lowest "begin" is the first line (line 1), and if the "end here" is a regex, then it is only attempted to match against on the next line after "begin", so the earliest possible end is line 2. So since range is inclusive, smallest possible range is "2 lines" and smallest starting range is both lines 1 and 2 (i.e. if there's an occurrence on line 1, occurrences on line 2 will also be changed, not desired in this case). GNU sed adds its own extension of allowing specifying start as the "pseudo" line 0 so that the end of the range can be line 1, allowing it a range of "only the first line" if the regex matches the first line.

或者一个简化版本(空的RE,如//,意味着重用之前指定的,所以这是等价的):

sed '0,/Apple/{s//Banana/}' input_filename

对于s命令,花括号是可选的,所以这也是等价的:

sed '0,/Apple/s//Banana/' input_filename

所有这些工作都只在GNU上进行。

你也可以使用homebrew brew install GNU -sed在OS X上安装GNU sed。

其他回答

用例可能是您的事件分布在整个文件中,但您知道您只关心前10、20或100行。

然后简单地处理这些行就可以解决问题——即使OP的措辞只是首先考虑。

sed '1,10s/#include/#include "newfile.h"\n#include/'

相当全面的linuxtopia常见问题解答集合。它还强调了人们提供的一些答案不能与非gnu版本的sed一起工作,例如

sed '0,/RE/s//to_that/' file

在非gnu版本中必须是

sed -e '1s/RE/to_that/;t' -e '1,/RE/s//to_that/'

但是,这个版本不能与gnu sed一起工作。

下面是一个两者都适用的版本:

-e '/RE/{s//to_that/;:a' -e '$!N;$!ba' -e '}'

ex:

sed -e '/Apple/{s//Banana/;:a' -e '$!N;$!ba' -e '}' filename

下面的命令删除文件中字符串的第一次出现。它还删除了空行。它显示在xml文件上,但它可以用于任何文件。

如果您使用xml文件并且想要删除一个标记,则非常有用。在本例中,它删除了第一次出现的“isTag”标记。

命令:

sed -e 0,/'<isTag>false<\/isTag>'/{s/'<isTag>false<\/isTag>'//}  -e 's/ *$//' -e  '/^$/d'  source.txt > output.txt

源文件:Source .txt

<xml>
    <testdata>
        <canUseUpdate>true</canUseUpdate>
        <isTag>false</isTag>
        <moduleLocations>
            <module>esa_jee6</module>
            <isTag>false</isTag>
        </moduleLocations>
        <node>
            <isTag>false</isTag>
        </node>
    </testdata>
</xml>

结果文件(output.txt)

<xml>
    <testdata>
        <canUseUpdate>true</canUseUpdate>
        <moduleLocations>
            <module>esa_jee6</module>
            <isTag>false</isTag>
        </moduleLocations>
        <node>
            <isTag>false</isTag>
        </node>
    </testdata>
</xml>

ps:它在Solaris SunOS 5.10(相当旧)上不能工作,但在Linux 2.6 sed版本4.1.5上可以工作

我需要一个在GNU和BSD上都可以工作的解决方案,而且我也知道第一行永远不会是我需要更新的一行:

sed -e "1,/pattern/s/pattern/replacement/"

尝试//特性不重复模式对我不起作用,因此需要重复它。

#!/bin/sed -f
1,/^#include/ {
    /^#include/i\
#include "newfile.h"
}

该脚本的工作原理:对于第1行和第一个#include(在第1行之后)之间的行,如果该行以#include开头,则在指定的行前面加上。

但是,如果第一个#include在第1行,那么第1行和下一个#include都将有该行前置。如果您正在使用GNU sed,它有一个扩展,其中0,/^#include/(而不是1,)将做正确的事情。