如何使用 sed 嵌套全局匹配?

如何使用 sed 嵌套全局匹配?

如果我做:

sed 's/match/replace/g'

我知道那sed会替代代替对于每一次出现匹配在一条线上。但如果……呢?

echo "match <please dont match this?>" |
sed 's/match/replace/g'

...或者...

echo "never match unless <the match is somehow delimited?>" |
sed 's/match/replace/g'

我知道我可以使用test 或branch 循环来单独递归匹配,但是如何在s///g全局匹配上下文中跳过一行的某些部分?

答案1

事情sed是这样的贪婪的。对于每种情况,它都会尽可能多地吞噬。这可以在s///g局部替换环境中为您带来优势。如果你们\(\) *零个或多个字符串的匹配,在任何情况下sed都会全局地吞噬那些第一个。g因此,如果您可以可靠地界定/匹配这个/ |跳过这个|如果你可以这样做:

sed 's/\([^<>]*<\)*\(match  *\)*\(remove  *\)*/\1/g
     s/.\{,45\}[^ ]*/&\
/g;  s/\(\n\) */\1/g
' <<INPUT
Never remove any match unless <the match \
you want to remove is somehow delimited.> \
And you can remove any match <per your match \
delimiter as many times as your match occurs \
within the match delimiters.>
INPUT

输出

Never remove any match unless <the you want to
is somehow delimited.> And you can remove any
match <per your delimiter as many times as your
occurs within the delimiters.>

那里的输入是一行,因为 shell 在反斜杠上转义了此处文档中的换行符。sed将其拆分为 45 个字符(给予或接受)边界并打印它。尽管如此,正如您所看到的,每次出现任一情况匹配或者消除外面一个<...>边界仍然存在,而所有内部的边界都从输出中删除。

这是 的贪婪函数sed,因为它适用于发生的匹配*零个或多个次。正是这种贪婪使得替换不可能以同样的方式进行,尽管这只需要额外的一两个步骤来否定。

为了清楚地了解其工作原理,我们可以执行替换 - 顺便说一下,如果直接应用,这通常不太有用,正如我的意思是:

printf '%s %s\n' '<321Nu0-9mber123>' \
                 'String321strinG' \
                 '<321Nu0-9mber123>' \
                 'String321strinG' |
sed 's/\(<[^<>]*>\)*[0-9]*/\1!/g'

输出

<321Nu0-9mber123>! !S!t!r!i!n!g!s!t!r!i!n!G!
<321Nu0-9mber123>! !S!t!r!i!n!g!s!t!r!i!n!G!

因此,当sed匹配全局模式上的行时,它会尝试尽可能多地匹配该模式,同时保持其特有的贪婪性。当模式为贪婪时的副作用零个或多个指定的出现次数与该行的某个部分不匹配仍然匹配- 它匹配空字符串之间无法匹配的行部分的字节。

上面你可以看到<...>字符串不受影响,而其中的数字细绳...不仅消失了,而且还sed为每个字符插入了一个刘海。这反映了sed每次与空字符串的匹配。正是由于这个原因,该技术对于g全球范围内都很有用。界定替换比赛而不是做一场。

这是它的工作原理:

printf '%s\t%s\n' '<321Nu0-9mber123>' \
                'String321strinG' \
                '<321Nu0-9mber123>' \
                'String321strinG' |
sed 's/[0-9]/&\n/g;s/\(<[^<>]*>\)*\n*/\1/g;y/\n/0/'

输出

<302010Nu00-90mber102030>       String321strinG
<302010Nu00-90mber102030>       String321strinG

<这会为and中出现的每个数字附加一个零>- 这是一个相当简单的情况 - 但实际上,您可以以\n这种方式使用 ewline 字符来执行全局替换任何匹配。基本原则是:

  1. sed 's/match/&\n/g'
  2. 然后做sed 's/\(match group\)*\n*/\1/g'
  3. 最后做的事sed 's/match\n/replace/g'

诚然,这些示例仅演示了平面列表示例 -<始终位于前面>。巢也需要考虑。它们更难——有时更难——但是,好吧……

sed 's/\([{}]\)\([^{}]*[{}]*\1\)*/\n<&>/g
' <<\INPUT
{{{1!}{2!}{3!}}}outside!{{{4!}}{{5!}}}
INPUT

输出

<{{{1!}{2!}{>3!
<}}}>outside!
<{{{4!}}{{>5!
<}}}>

它在换行符上序列化组。它的工作原理是交替每个匹配组匹配的分隔符,同时连续两次堆叠尽可能多的同类分隔符(至少两次)一个副作用是比较开盘和收盘。也就是说,为了简单起见,其余部分将假设任何读者都会使用类似的方法来准备输入,并且嵌套不是问题。

本质上,所有这一切的操作思想都是匹配优先级。第一个示例的工作原理是在尝试匹配删除字符串之前尝试匹配紧邻开放分隔符之前的任何非分隔符字符组。按理说,如果第一组匹配,那么当替换完成时,整个匹配组只能被其本身替换 - 这就是替换变得困难的原因。删除更简单,因为当您匹配它们时,您只需将它们排除在替换之外即可,一切都很好。

sed比其他类型更重视某些类型的模式。重要的是要了解,当您执行此操作时确实指定的模式总是比一个模式承载更多的权重*零个或多个案件。因此,当您将这些用于全局模式时,仅使用*或根本不使用它们 - 否则您可能最终根本不会跳过任何组。

这就是你如何做到这一点sed

相关内容