使用“sed”在正则表达式匹配后搜索并替换行

使用“sed”在正则表达式匹配后搜索并替换行

这种感觉在 中会更容易awk,但我很好奇是否sed可以做到。这是我的输入:

line 1
line 2
line 3
line 1
line 2
line 3
line 1
line 2
line 3

我想编写一个就地正则表达式来查找第二个line 1,然后替换line 3之后找到的所有内容。输出如下所示:

line 1
line 2
line 3
line 1
line 2
replaced
line 1
line 2
replaced

我并不是真的在寻找仅适用于此输入的“聪明”解决方案。我想了解是否有通用的方法来搜索和替换与 的比赛sed

我以为解决方案就在某个地方文档addr,但它似乎没有描述/starting point/,s/...为您可以做的事情,并且当我尝试这样做时出现错误。

答案1

为此,您需要计算已经看到目标线的次数。不幸的是,把任何东西算进去sed都是 PITA。你最好在awkor中这样做perl。例如:

$ perl -p -e '$found++ if m/line 1/;
              next if $found < 2;
              s/line 3/replaced/' input.txt 
line 1
line 2
line 3
line 1
line 2
replaced
line 1
line 2
replaced

或在awk

$ awk '/line 1/ { found++ };
       found < 2 { print ; next };
       { sub(/line 3/,"replaced") ; print }' input.txt 
line 1
line 2
line 3
line 1
line 2
replaced
line 1
line 2
replaced

这些方法有效,但远非最佳解决方案,尤其是awk版本(只是 perl 的直接翻译)。

顺便说一句,让任何一个版本都使用正则表达式来搜索和数数,要搜索的正则表达式和代替、替换字符串,甚至所需的计数都作为命令行选项,而不是在脚本中对它们进行硬编码。

答案2

使用 GNU sed(对于 0 地址1):

$ sed '0,/^line 1$/b; /^line 1$/,$ s/^line 3$/replaced/' < file
line 1
line 2
line 3
line 1
line 2
replaced
line 1
line 2
replaced

我们b牧场出去对于第一个0,/^line 1/范围,这意味着第二个范围/^line 1$/,$仅在到达第二次出现 时才看到其起始行line 1

使用awk,可以更轻松地发现 的第 n出现line 1

awk '$0 == "line 1" && ++n == 2, eof {if ($0 == "line 3") $0 = "replaced"}; 1'

(这里eof只是任何其他未初始化的变量,因此会产生错误的来表示第一的,最后的范围没有尽头;与0字面意思相同,但更易读;类似地,我们可以使用!skip而不是1来表示真的对于要打印的当前记录,但使用1是足够惯用的,它应该被所有用户识别awk,并且更清晰的替代方案是{print})。


1 并且由于我们假设 GNU sed,我们也可以在 1;more commands之后添加b,这也不是标准的。我们需要另一个-e 'more commands'和其他sed的。

答案3

在缓冲区(模式空间)中创建一个计数器并在每一行上检查它:

sed '
/^line 1/{x;/11/!s/^/1/;x}
x;/11/{x;b1};x;b
:1;s/^line 3$/replaced/
' file

我们用作1计数器单位

[更新] 更好的性能:

sed '
/^line 1$/{x;/11/!s/^/1/;x}
/^line 3$/!b
x;/11/!{x;b}
x;s/.*/replaced/
' file

仅在与模式匹配的行中检查计数器 -/^line 3$/

答案4

使用 GNU sed,我们可以通过将计数存储为.保存空间中的点来执行任务。

sed -e '
  /^line 1$/,/^line 1$/{//!b
    x;s/^$/./;x;t
    :a;n;s/^line 3$/REPLACED/;ba
  }
' file

另一种可能性是不使用范围运算符并使用 GNU sed 中的扩展正则表达式模式。这里换行符\n作为计数器,存储在保留空间中。

sed -Ee '
  /^line 1$/H
  x;s/^(\n{1,2}).*/\1/;x
  /^line 3$/G
  /\n.{2}/c REPLACED
  s/\n//g
' file

Perl 可以用类似的方式做同样的事情

perl -lpe '
  s/^line 3$/$a?"REPLACED":$&/e
           unless
  m?^line 1$? ... /^line 1$(?{$a++})/;
' file

使用 awk,我们可以很好地将正则表达式与变量结合起来并递增一

awk '
1 < ( k += /^line 1$/ ) &&
sub(/^line 3$/,"REPLACED") ||
1' file

相关内容