我想在任何现有的#include之前,用一个额外的include指令更新大量的c++源文件。对于这类任务,我通常使用带有sed的小型bash脚本来重写文件。
我如何得到sed替换一个文件中字符串的第一次出现,而不是替换每一次出现?
如果我用
sed s/#include/#include "newfile.h"\n#include/
它替换所有#includes。
我们也欢迎为达到同样目的而提出的其他建议。
我想在任何现有的#include之前,用一个额外的include指令更新大量的c++源文件。对于这类任务,我通常使用带有sed的小型bash脚本来重写文件。
我如何得到sed替换一个文件中字符串的第一次出现,而不是替换每一次出现?
如果我用
sed s/#include/#include "newfile.h"\n#include/
它替换所有#includes。
我们也欢迎为达到同样目的而提出的其他建议。
当前回答
使用GNU sed的-z选项,您可以像处理一行文件一样处理整个文件。这样,s/…/…/将只替换整个文件中的第一个匹配项。记住:s/…/…/只替换每行中的第一个匹配项,但是使用-z选项sed将整个文件视为一行。
sed -z 's/#include/#include "newfile.h"\n#include'
在一般情况下,您必须重写sed表达式,因为模式空间现在包含整个文件,而不仅仅是一行。一些例子:
s /文本。*//可以重写为s/text[^\n]*//。[^\n]匹配除换行符以外的所有内容。[^\n]*将匹配文本后的所有符号,直到出现换行符。 S /^text//可以重写为S /(^|\n)text//。 S /text$//可以重写为S /text(\n|$)//。
其他回答
这可能为你工作(GNU sed):
sed -si '/#include/{s//& "newfile.h\n&/;:a;$!{n;ba}}' file1 file2 file....
或者如果内存不是问题:
sed -si ':a;$!{N;ba};s/#include/& "newfile.h\n&/' file1 file2 file...
我终于得到了这个工作在一个Bash脚本用来插入一个唯一的时间戳在每个项目在RSS提要:
sed "1,/====RSSpermalink====/s/====RSSpermalink====/${nowms}/" \
production-feed2.xml.tmp2 > production-feed2.xml.tmp.$counter
它只改变第一个事件。
${nowms}是Perl脚本设置的以毫秒为单位的时间,$counter是脚本中用于循环控制的计数器,\允许命令在下一行继续执行。
文件被读入,stdout被重定向到工作文件。
我理解它的方式,1,/====RSSpermalink====/告诉sed什么时候通过设置范围限制来停止,然后s/====RSSpermalink====/${nowms}/是熟悉的sed命令,用第二个字符串替换第一个字符串。
在我的例子中,我把命令放在双引号中,因为我在一个带有变量的Bash脚本中使用它。
你可以使用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更容易理解:-)
# sed script to change "foo" to "bar" only on the first occurrence
1{x;s/^/first/;x;}
1,/foo/{x;/first/s///;x;s/foo/bar/;}
#---end of script---
或者,如果你喜欢:编辑注:只适用于GNU sed。
sed '0,/foo/s//bar/' file
源
sed '0,/pattern/s/pattern/replacement/' filename
这对我很管用。
例子
sed '0,/<Menu>/s/<Menu>/<Menu><Menu>Sub menu<\/Menu>/' try.txt > abc.txt
编者注:两者都只适用于GNU sed。