我想在任何现有的#includes之前用额外的include指令更新大量的C++源文件.对于这种任务,我通常使用带有sed的小bash脚本来重写文件.
如何sed
更换文件中第一次出现的字符串而不是替换每次出现?
如果我使用
sed s/#include/#include "newfile.h"\n#include/
它取代了所有#includes.
也欢迎提供相同建议的替代建议.
写一个sed脚本,只会用"Banana"替换第一次出现的"Apple"
输入示例:输出:
Apple Banana Orange Orange Apple Apple
这是一个简单的脚本:编者注:仅适用于GNU sed
.
sed '0,/Apple/{s/Apple/Banana/}' filename
# 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,/RE/s//to_that/' file
资源
sed '0,/pattern/s/pattern/replacement/' filename
这对我有用.
例
sed '0,/
编者注:两者都只适用于GNU sed
.
一个概述了许多有益的现有的答案,与补充说明:
这里的示例使用简化的用例:仅在第一个匹配行中将'foo'替换为'bar'.
由于使用的ANSI C引号字符串($'...'
),以提供所述样品输入线,bash
,ksh
,或zsh
假定为壳.
sed
仅限GNU:
本Hoffstein的anwswer告诉我们,GNU提供了一个扩展的POSIX规范sed
,允许下列2地址形式:0,/re/
(re
代表一个任意的正则表达式在这里).
0,/re/
允许正则表达式在第一行匹配.换句话说:这样的地址将创建从第1行到包括匹配行的范围re
- 无论re
是在第1行还是在任何后续行上.
与POSIX兼容的形式对比这1,/re/
,它创建了一个从第1行直到并包括相匹配的匹配线范围re
上后续线; 换句话说:如果它碰巧发生在第一行,这将不会检测到re
匹配的第一次出现,并且还阻止使用速记//
来重用最近使用的正则表达式(参见下一点).[1]
如果将0,/re/
地址与s/.../.../
使用相同正则表达式的(替换)调用组合在一起,则命令将仅在匹配的第一行上执行替换re
.为重用最近应用的正则表达式
sed
提供了一个方便的快捷方式:空分隔符对,//
.
$ sed '0,/foo/ s//bar/' <<<$'1st foo\nUnrelated\n2nd foo\n3rd foo'
1st bar # only 1st match of 'foo' replaced
Unrelated
2nd foo
3rd foo
仅限POSIX功能sed
,如BSD(macOS)sed
(也适用于GNU sed
):
由于0,/re/
不能使用并且表格1,/re/
不会检测re
是否恰好发生在第一行(见上文),因此需要对第1行进行特殊处理.
MikhailVS的回答提到了这种技术,在这里举了一个具体的例子:
$ sed -e '1 s/foo/bar/; t' -e '1,// s//bar/' <<<$'1st foo\nUnrelated\n2nd foo\n3rd foo'
1st bar # only 1st match of 'foo' replaced
Unrelated
2nd foo
3rd foo
注意:
空的正则表达式//
快捷方式在这里使用两次:一次用于范围的端点,一次用于s
调用; 在这两种情况下,regex都foo
被隐式重用,允许我们不必复制它,这使得更短和更易维护的代码.
POSIX sed
在某些功能之后需要实际的换行符,例如在标签名称之后甚至是其遗漏之后,就像t
这里的情况一样; 策略性地将脚本拆分为多个-e
选项是使用实际换行符的替代方法:结束每个-e
脚本块通常需要换行的位置.
1 s/foo/bar/
foo
如果在那里找到,只替换在第一行.如果是这样,则t
分支到脚本的末尾(跳过该行上的剩余命令).(t
仅当最近的s
调用执行实际替换时,该函数才会分支到标签;如果没有标签,则此处的情况就是脚本的末尾分支到).
发生这种情况时,范围地址1,//
(通常从第2行开始查找第一个匹配项)将不匹配,并且不会处理范围,因为在当前行已经计算时会计算地址2
.
相反,如果第一行没有匹配,1,//
则输入,并找到真正的第一场比赛.
净效果是一样的与GNU sed
的0,/re/
:只有第一发生替换,不管它发生在第一线或任何其他.
非范围方法
potong的回答演示了绕过范围需求的循环技术 ; 因为他使用GNU语法,所以这里是符合POSIX的等价物: sed
循环技术1:在第一次匹配时,执行替换,然后输入一个循环,只是按原样打印剩余的行:
$ sed -e '/foo/ {s//bar/; ' -e ':a' -e '$!{n;ba' -e '};}' <<<$'1st foo\nUnrelated\n2nd foo\n3rd foo'
1st bar
Unrelated
2nd foo
3rd foo
循环技术2,仅适用于小文件:将整个输入读入内存,然后对其执行单个替换.
$ sed -e ':a' -e '$!{N;ba' -e '}; s/foo/bar/' <<<$'1st foo\nUnrelated\n2nd foo\n3rd foo'
1st bar
Unrelated
2nd foo
3rd foo
[1] 1.61803提供1,/re/
了随后出现的情况的例子s//
:
- sed '1,/foo/ s/foo/bar/' <<<$'1foo\n2foo'
产量$'1bar\n2bar'
; 即两条线都被更新,因为线号1
与第一条线匹配,而正则表达式/foo/
- 范围的结束 - 仅在下一条线上开始查找.因此,在这种情况下选择两条线,并且s/foo/bar/
对它们两者执行替换.
- sed '1,/foo/ s//bar/' <<<$'1foo\n2foo\n3foo'
失败:使用sed: first RE may not be empty
(BSD/macOS)和sed: -e expression #1, char 0: no previous regular expression
(GNU),因为在处理第一行时(由于行号1
开始于该范围),尚未应用正则表达式,因此//
不引用任何内容.
除了GNU sed
的特殊0,/re/
语法之外,任何以行号开头的范围都会有效地排除使用//
.
你可以使用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,因此我们不添加更多包含.
1;
这意味着"打印出行" - 空行动默认打印$ 0,打印出整行.一个班轮,比sed IMO更容易理解:-)
关于linuxtopia sed FAQ的全面答案.它还强调了人们提供的一些答案不适用于非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 '}'
例如:
sed -e '/Apple/{s//Banana/;:a' -e '$!N;$!ba' -e '}' filename
#!/bin/sed -f 1,/^#include/ { /^#include/i\ #include "newfile.h" }
此脚本的工作原理:对于1和第1行之间的#include
行(第1行之后),如果行以该行开头#include
,则在前面添加指定的行.
但是,如果第一#include
行在第1行,那么第1行和下一#include
行将在前面添加该行.如果你正在使用GNU sed
,它有一个扩展,其中0,/^#include/
(而不是1,
)将做正确的事情.
只需在最后添加出现次数:
sed s/#include/#include "newfile.h"\n#include/1
可能的解决方案:
/#include/!{p;d;} i\ #include "newfile.h" : n b
说明:
读取行直到我们找到#include,打印这些行然后开始新的循环
插入新的包含行
进入一个只读取行的循环(默认sed也会打印这些行),我们不会从这里回到脚本的第一部分