我想在任何现有的#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

其他回答

只需要在最后加上出现的次数:

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

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

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

这里可能的解决方案是告诉编译器包含头文件,而不在源文件中提到它。在GCC中有这些选项:

   -include file
       Process file as if "#include "file"" appeared as the first line of
       the primary source file.  However, the first directory searched for
       file is the preprocessor's working directory instead of the
       directory containing the main source file.  If not found there, it
       is searched for in the remainder of the "#include "..."" search
       chain as normal.

       If multiple -include options are given, the files are included in
       the order they appear on the command line.

   -imacros file
       Exactly like -include, except that any output produced by scanning
       file is thrown away.  Macros it defines remain defined.  This
       allows you to acquire all the macros from a header without also
       processing its declarations.

       All files specified by -imacros are processed before all files
       specified by -include.

微软的编译器有/FI(强制包含)选项。

这个特性对于一些常见的头文件来说很方便,比如平台配置。Linux内核的Makefile为此使用-include。

一个可能的解决方案:

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

解释:

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

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

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

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