使用sed在匹配项后插入行

331

由于某些原因,我似乎找不到一个简单明了的答案,而我现在时间比较紧迫。如何使用sed命令在找到第一个与特定字符串匹配的行后插入一行选择文本。我有...

CLIENTSCRIPT="foo"
CLIENTFILE="bar"

我希望您在CLIENTSCRIPT=行后插入一行,从而得到...

CLIENTSCRIPT="foo"
CLIENTSCRIPT2="hello"
CLIENTFILE="bar"

1
如果需要在插入的行中使用捕获组,请查看此问题 https://dev59.com/2Zrga4cB1Zd3GeqPiBsl - akostadinov
8个回答

473

尝试使用 GNU sed 进行此操作:

sed '/CLIENTSCRIPT="foo"/a CLIENTSCRIPT2="hello"' file

如果您想进行原地替换,请使用

sed -i '/CLIENTSCRIPT="foo"/a CLIENTSCRIPT2="hello"' file

输出

CLIENTSCRIPT="foo"
CLIENTSCRIPT2="hello"
CLIENTFILE="bar"

文档


19
请注意,两者都假定使用GNU的sed。这不是标准的sed语法,并且不适用于任何其他sed实现。 - Stephane Chazelas
3
我遇到了一些问题,因为我使用了不同的定界符。在阅读完说明文档后,我意识到我必须以 \ 加上定界符来开始正则表达式。 - Christy
12
它是如何仅应用于第一次匹配的?显然它会在匹配后追加文本,但它是如何知道仅适用于第一次匹配的呢? - Mohammed Noureldin
10
对我来说,这会在每次匹配后添加文本。 - schoppenhauer
1
有没有办法保留缩进? - Zanidd
显示剩余9条评论

71

请注意标准的sed语法(如POSIX所述,因此受所有符合条件的sed实现支持(包括GNU、OS / X、BSD、Solaris等)):

sed '/CLIENTSCRIPT=/a\
CLIENTSCRIPT2="hello"' file

或者在一行上:

sed -e '/CLIENTSCRIPT=/a\' -e 'CLIENTSCRIPT2="hello"' file

(-e表达式(以及-f文件的内容)将连接成一个字符串,每行末尾加上换行符,作为sed脚本提供给sed解释执行。)

-i选项用于原地编辑也是GNU扩展,一些其他实现(如FreeBSD)支持-i''来实现同样功能。

或者,为了可移植性,您可以使用perl代替:

perl -pi -e '$_ .= qq(CLIENTSCRIPT2="hello"\n) if /CLIENTSCRIPT=/' file

或者你可以使用 edex

printf '%s\n' /CLIENTSCRIPT=/a 'CLIENTSCRIPT2="hello"' . w q | ex -s file

2
我可能错了,但是当前的 sed -e '/CLIENTSCRIPT=/a\' -e 'CLIENTSCRIPT2="hello"' file 转义了第一个参数结尾处的引号并破坏了命令。 - Abandoned Cart
1
在 Bourne、cshrc 家族的 shell 中,'...' 是强引用,其中反斜杠不是特殊字符。我所知道的唯一例外是 fish shell。 - Stephane Chazelas
2
在MacOS上的终端(以及继而在终端中运行的脚本)显然也是个例外。我已经找到了替代语法,但还是谢谢。 - Abandoned Cart
1
@AbandonedCart,那是另一回事。这是macOS上的sed不符合POSIX标准。这使得我对其可移植性的说法是不正确的(对于单行变体)。如果它确实是非一致性或者是我的标准误解,我会向opengroup询问确认。 - Stephane Chazelas
我曾经遇到过很多不同版本的sed问题,所以我尝试了printf...ex版本。这个版本非常好用! - Matthias Bohlen

20

适用于MacOS(至少是OS 10)和Unix的Sed命令(即不像Gilles'(当前被接受的)那样需要gnu sed):

sed -e '/CLIENTSCRIPT="foo"/a\'$'\n''CLIENTSCRIPT2="hello"' file

这适用于bash和其他支持$'\n'评估引号样式的shell。一切都可以在一行中完成,并且适用于旧的/POSIX sed命令。如果可能会有多行匹配CLIENTSCRIPT="foo"(或类似的内容),并且您希望仅在第一次添加额外行,则可以按如下方式重新制作:

sed -e '/^ *CLIENTSCRIPT="foo"/b ins' -e b -e ':ins' -e 'a\'$'\n''CLIENTSCRIPT2="hello"' -e ': done' -e 'n;b done' file

(这在插入代码后创建了一个循环,使其只循环遍历文件的其余部分,永远无法回到第一个sed命令)。

您可能会注意到,在匹配模式中加入了'^ *',以防该行出现在注释中或缩进。虽然不是100%完美,但涵盖了一些其他常见情况。根据需要进行调整...

这两个解决方案也解决了(对于添加行的通用解决方案)的问题,即如果新插入的行包含未转义的反斜杠或和号,则它们将被sed解释,并且很可能不能相同,就像\n一样-例如,\0将是第一行匹配。特别适用于添加来自变量的行的情况,在这种情况下,您否则必须使用${var//}先转义所有内容,或者使用另一个sed语句等。

这个解决方案在脚本中有点混乱(那里的引用和 \n 很难读),当您不想在起始行上放置执行a命令的替换文本时,比如说在具有缩进行的函数中。我利用了$'\n'被shell评估为换行符的优势,而不是常规的'\n'单引号值。

虽然已经足够长了,但我认为perl / awk可能会因为更易读而获胜。


1
谢谢你的回答!第一个在AIX操作系统上也能正常运行。 - abhishek
如何实现多行插入? - tatsu
1
POSIX sed支持在替换字符串中使用字面换行,如果您用反斜杠进行“转义”([来源](http://man7.org/linux/man-pages/man1/sed.1p.html))。所以:`s/CLIENTSCRIPT="foo"/&\ **[Enter]**CLIENTSCRIPT2="hello"/`。反斜杠必须是该行的最后一个字符(不能在其后面有空格),与其在此注释中的外观相反。 - TheDudeAbides
1
@tatsu s/// 替换字符串中的换行符,以及 ai 命令中的换行符可以使用我在上面评论中描述的相同方法进行“转义”。 - TheDudeAbides

20
一个符合 POSIX 标准的,使用 s 命令的实现:
sed '/CLIENTSCRIPT="foo"/s/.*/&\
CLIENTSCRIPT2="hello"/' file

1
它甚至支持插入新行。我喜欢它! - Stephan Henningsen
1
另请参阅 sed -e '/.../s/$/\' -e 'CLI.../',这将避免包含无效字符字节序列的行出现问题。 - Stephane Chazelas

11
也许现在回答有点晚了,但我发现上面的一些解决方案有点繁琐。 我尝试使用sed进行简单字符串替换,它起作用了。
sed 's/CLIENTSCRIPT="foo"/&\nCLIENTSCRIPT2="hello"/' file

&符号反映了匹配的字符串,然后添加\n和新行。

如前所述,如果想要原地执行:

sed -i 's/CLIENTSCRIPT="foo"/&\nCLIENTSCRIPT2="hello"/' file

另外一件事。你可以使用一个表达式进行匹配:

sed -i 's/CLIENTSCRIPT=.*/&\nCLIENTSCRIPT2="hello"/' file

希望这能帮助到某人。


这在Linux上运行良好,因为GNU sed是默认的,它可以理解替换字符串中的\n。然而,普通的(POSIX)sed不理解替换字符串中的\n,所以在BSD或macOS上无法工作——除非你已经安装了GNU sed。使用普通的sed,您可以通过“转义”来嵌入换行符——也就是说,在行末加上反斜杠,然后按**[Enter]**键(参考,在[2addr]s/BRE/replacement/flags标题下)。这意味着您的sed命令必须跨越终端的多行。 - TheDudeAbides
在替换字符串中获取一个字面上的换行符的另一种选择是使用 Bash 中的 ANSI-C quoting 功能,正如 其他答案 中提到的那样。 - TheDudeAbides

7
awk变体:
awk '1;/CLIENTSCRIPT=/{print "CLIENTSCRIPT2=\"hello\""}' file

2
对于任何想知道 1; 的人,请参考 为什么 Awk 中的“1”会打印当前行? - cherdt
谢谢,如何只打印第一个匹配项之后的内容? - Sumit Jain

1
我有一个类似的任务,但无法让上面的Perl解决方案工作。
以下是我的解决方案: perl -i -pe "BEGIN{undef $/;} s/^\[mysqld\]$/[mysqld]\n\ncollation-server = utf8_unicode_ci\n/sgm" /etc/mysql/my.cnf
解释:
使用正则表达式在/etc/mysql/my.cnf文件中搜索只包含[mysqld]的一行,并将其替换为
[mysqld] collation-server = utf8_unicode_ci
有效地在包含[mysqld]的行后添加了collation-server = utf8_unicode_ci行。

0
最近我也需要在Mac和Linux操作系统上完成这个任务。在浏览了许多帖子并尝试了很多方法后,我个人认为我没有达到我想要的目标:使用众所周知和标准命令、简单模式的足够简单易懂的解决方案,一行代码,可移植,可扩展以添加更多约束条件。然后我试图从不同的角度看待它,那时我意识到如果“两行代码”符合我的其他标准,我可以不用“一行代码”选项。最终,我想出了这个解决方案,在Ubuntu和Mac上都能正常工作,我想与大家分享。
insertLine=$(( $(grep -n "foo" sample.txt | cut -f1 -d: | head -1) + 1 ))
sed -i -e "$insertLine"' i\'$'\n''bar'$'\n' sample.txt

在第一个命令中,grep查找包含“foo”的行号,cut/head选择第一个出现的实例,并且算术操作增加了该第一个实例行号1,因为我想在该出现后进行插入。 在第二个命令中,这是一个就地文件编辑,“i”用于插入:ansi-c引用新行、“bar”,然后另一行新行。结果是在“foo”行后添加了一个包含“bar”的新行。这两个命令每个都可以扩展到更复杂的操作和匹配。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接