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

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

如果我用

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

它替换所有#includes。

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


当前回答

使用FreeBSD ed,避免ed在文件中没有include语句时出现“no match”错误:

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

# using FreeBSD ed
# to avoid ed's "no match" error, see
# *emphasized text*http://codesnippets.joyent.com/posts/show/11917 
cat <<-'EOF' | sed -e 's/^ *//' -e 's/ *$//' | ed -s <(echo "$teststr")
   H
   ,g/# *include/u\
   u\
   i\
   #include "newfile.h"\
   .
   ,p
   q
EOF

其他回答

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

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

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

一个可能的解决方案:

    /#include/!{p;d;}
    i\
    #include "newfile.h"
    :a
    n
    ba

解释:

读取行,直到找到#include,打印这些行,然后开始新的循环 插入新的包含行 输入一个只读取行(默认情况下sed也会打印这些行)的循环,我们不会从这里回到脚本的第一部分

sed -e 's/pattern/REPLACEMENT/1' <INPUTFILE

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

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/。

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