如何使用sed进行多行替换?

4

我有一个名为"a6s"的文本文件,其中包含多行内容:

y
n
yyy
n
y
yyy
yy
y
yyy
n

我使用了这个脚本:

$ sed -i "s/y$^y/ya\ny/g" a6s;

我希望在上一行结尾和下一行开头的两个“y”字符之间插入内容以输出:
y
n
yyy
n
ya
yyya
yya
ya
yyy
n

抱歉,我的英语不好!感谢Sundeep。 - Merson Su
3个回答

2

使用GNU sed(感谢@Sundeep提供的完美解决方案):

sed -i '/y$/ {N; s/y\ny/ya\ny/; P; D}' a6s

为了在多行中使用sed,您需要使用NPD命令:
  • {cmds}:一组命令
  • /y$/ {cmds}:只有当行以y结尾时才执行命令组
  • N:读取下一行到模式空间
  • s/regex/replacement/:进行替换,不需要g标志
  • P:打印模式空间直到第一个\n字符(第一行)
  • D:删除模式空间直到第一个\n字符

我的原始解决方案使用了不必要的标签:

sed -i ':a; N; s/y\ny/ya\ny/; {P; D}; ba' a6s

我建议查看这个关于处理多行的优秀教程:grymoire.com

感谢 PesaThe 和 Sundeep。 - Merson Su
2
由于您只比较最多两行,并且替换命令适用于两行,因此也许 sed 'N;s/y\ny/ya\ny/;P;D' file 就足够了? - potong
1
@potong 是的,那也可以。 /$y/ 只有在不需要它们的情况下才会跳过命令 :) - PesaThe
感谢potong。你的脚本很简洁。 - Merson Su

1

sed只能对单个行进行简单替换,仅此而已。当你试图用它做其他任何事情时,你就在使用过时的符文,在20世纪80年代中期发明awk后就变得多余了。使用GNU awk进行多字符RS操作:

$ awk -v RS='^$' -v ORS= '{while(sub(/y\ny/,"ya\ny"));} 1' file
y
n
yyy
n
ya
yyya
yya
ya
yyy
n

上述代码的作用很明显 - 循环遍历输入,将每个y\ny替换为ya\ny,直到没有要替换的y\ny

1
@PesaThe 这类似于 sed -z ':a s/y\ny/ya\ny/g; ta'(假设输入没有 NUL 字符)... 你不能在这里使用 gsub,因为一旦 y\ny 被替换,第二个 y 就不会再与下一行匹配。 - Sundeep
1
@Sundeep,所以对于例如y\ny\ny它不会正确工作,明白了,谢谢 :) - PesaThe
1
awk 可以使用 gsub(),但请记住,在任何工具中,与正则表达式匹配的字符串不能成为同一调用中下一个正则表达式段的一部分,因此 sed 's/yy/yay/g' 不会将 yyy 更改为 yayay,您需要 sed 's/yy/yay/g; s/yy/yay/g' 才能实现。因此,我本可以使用 gsub(),但它不会完成每个替换,我需要循环或两次调用才能完成,因此我决定使用循环来避免冗余,并且当您有循环时,sub() 与 gsub() 一样好,并且在我看来更清晰。 - Ed Morton
任何工具--> 任何工具评估所讨论的正则表达式。在y\n(?=y)末尾的字符串y不会成为正则表达式匹配的一部分,但我不确定如何表述我的前面陈述,以更清楚地表达我正在谈论的是包含在正则表达式匹配中的字符串,而不仅仅是说这个正则表达式。我怀疑这个观点已经被表达了。 - Ed Morton
1
很清楚 :) 我在试图发表一个俏皮的评论.. 应该加上 /s 或一些笑脸.. - Sundeep
显示剩余3条评论

0

将输入文件转换为一行并不是一个简单的解决方案:您需要对文件进行两次处理:

tr '\n' '\r' < a6s | sed ':a;s/y\ry/ya\ry/g;ta' | tr '\r' '\n'
or
tr '\n' '\r' < a6s | sed 's/y\ry/ya\ry/g;s/y\ry/ya\ry/g' | tr '\r' '\n'

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