许多有用的现有答案的概述,并辅以解释:
此处的示例使用了一个简化的用例:仅在第一个匹配行中将单词 'foo' 替换为 'bar'。
由于使用ANSI C-quoted strings ($'...') 提供示例输入行,bash、ksh 或zsh 被假定为shell。
GNU 仅限sed:
Ben Hoffstein's anwswer 向我们展示了 GNU 为POSIX specification for sed 提供了一个扩展,它允许以下2-address form:0,/re/(re 在这里代表任意正则表达式)。
0,/re/ 允许正则表达式匹配第一行。换句话说:这样的地址将创建一个范围,从第一行到匹配re 的行(包括re)——无论re 出现在第一行还是任何后续行。
- 将此与符合 POSIX 的表单
1,/re/ 进行对比,后者创建的范围从第一行到匹配 后续 上的 re 的行(包括) em> 行;换句话说:如果 re 匹配恰好出现在 1st 行,则此 不会检测到第一次出现的匹配,并且还 防止使用速记// 用于重用最近使用的正则表达式(见下一点)。1
如果您将0,/re/ 地址与使用same 正则表达式的s/.../.../(替换)调用结合起来,您的命令将仅在first em> 匹配 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,需要对第一行进行特殊处理强>。
MikhailVS's answer提到了技术,这里举个具体的例子:
$ 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 调用;在这两种情况下,正则表达式 foo 都被隐式重用,让我们不必复制它,这使得代码更短且更易于维护。
-
POSIX sed 在某些函数之后需要实际的换行符,例如在标签的名称之后,甚至在它的省略之后,就像这里的t 一样;策略性地将脚本拆分为多个 -e 选项是使用实际换行符的替代方法:在通常需要换行符的地方结束每个 -e 脚本块。
1 s/foo/bar/ 仅替换第一行的foo(如果在那里找到)。
如果是这样,t 会跳转到脚本的末尾(跳过该行的剩余命令)。 (仅当最近的 s 调用执行了实际替换时,t 函数才会分支到标签;在没有标签的情况下,就像这里的情况一样,脚本的结尾会分支到)。
当这种情况发生时,范围地址1,//,通常会找到第一个匹配项从第 2 行开始,将不匹配,并且范围将不匹配 被处理,因为当当前行已经是2 时,地址被评估。
反之,如果第一行没有匹配,1,//会被输入,并且会找到真正的第一个匹配。
最终效果与 GNU sed 的 0,/re/ 相同:仅替换第一个匹配项,无论它出现在第一行还是其他任何位置。
非范围进近
potong's answer 演示 循环 技术,绕过对范围的需求;因为他使用 GNU sed 语法,这里是 POSIX 兼容的等价物:
循环技术 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
11.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' fails:使用 sed: first RE may not be empty (BSD/macOS) 和 sed: -e expression #1, char 0: no previous regular expression (GNU),因为当时正在处理第一行(由于行号 1 开始range),还没有应用正则表达式,所以// 没有引用任何东西。
除了 GNU sed 的特殊 0,/re/ 语法之外,以 行号 开头的 any 范围实际上排除了 // 的使用。