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

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

如果我用

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

它替换所有#includes。

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


当前回答

下面的命令删除文件中字符串的第一次出现。它还删除了空行。它显示在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上可以工作

其他回答

sed '0,/pattern/s/pattern/replacement/' filename

这对我很管用。

例子

sed '0,/<Menu>/s/<Menu>/<Menu><Menu>Sub menu<\/Menu>/' try.txt > abc.txt

编者注:两者都只适用于GNU sed。

你可以使用awk来做类似的事情。

awk '/#include/ && !done { print "#include \"newfile.h\""; done=1;}; 1;' file.c

解释:

/#include/ && !done

当行匹配“#include”并且我们还没有处理它时,在{}之间运行操作语句。

{print "#include \"newfile.h\""; done=1;}

这将打印#include "newfile.h",我们需要转义引号。然后我们将done变量设置为1,这样我们就不会添加更多的include。

1;

这意味着“打印出行”——一个空操作默认为打印$0,打印出整行。一个简单的程序,比sed更容易理解:-)

作为一种替代建议,您可能希望查看ed命令。

man 1 ed

teststr='
#include <stdio.h>
#include <stdlib.h>
#include <inttypes.h>
'

# for in-place file editing use "ed -s file" and replace ",p" with "w"
# cf. http://wiki.bash-hackers.org/howto/edit-ed
cat <<-'EOF' | sed -e 's/^ *//' -e 's/ *$//' | ed -s <(echo "$teststr")
   H
   /# *include/i
   #include "newfile.h"
   .
   ,p
   q
EOF

如果有人来这里替换所有行中第一次出现的字符(比如我自己),使用这个:

sed '/old/s/old/new/1' file

-bash-4.2$ cat file
123a456a789a
12a34a56
a12
-bash-4.2$ sed '/a/s/a/b/1' file
123b456a789a
12b34a56
b12

例如,通过将1改为2,你可以只替换所有的第二个a。

我知道这是一个老帖子,但我有一个解决方案,我过去经常使用:

grep -E -m 1 -n 'old' file | sed 's/:.*$//' - | sed 's/$/s\/old\/new\//' - | sed -f - file

基本上使用grep打印第一次出现并在那里停止。另外,打印行号即5:行。将其导入sed并删除:和后面的所有内容,这样就只剩下行号了。将其导入sed,从而添加s/。*/replace到结束数字,这将导致一个1行脚本,该脚本被管道插入到最后一个sed中,作为脚本在文件上运行。

因此,如果regex = #include and replace = blah,并且grep发现的第一个出现在第5行,那么传输到最后一个sed的数据将是5s/.*/blah/。

即使第一次出现在第一行也有效。