递归替换Sed

18

echo ddayaynightday | sed 's/day//g'

运行结果为 daynight

有没有办法使其替换到没有匹配为止?


重复操作直到其长度不再改变。不知道如何在Shell中实现。 - kirilloid
6个回答

38

我个人偏爱的形式,针对这种情况:

echo ddayaynightday | sed -e ':loop' -e 's/day//g' -e 't loop'

这个与其他人的相同,只是使用了多个-e命令来制作三行,并使用t结构——这意味着“如果您进行了成功的替换,则分支”——进行迭代。


1
很好知道如何在命令行中输入多个脚本行,以防“;”无法使用的情况。我更喜欢你的答案,因为t条件跳转对我来说更清晰,而不是无条件跳转的模式匹配。 - Christian Semrau
谢谢 :) 我认为这看起来不错,不确定是否有更好的方法使用一些扩展正则表达式.. - w00d

8
这个可能适用于你:
echo ddayaynightday | sed ':a;s/day//g;ta'
night

似乎你需要多个“-e”开关。 - potong

4
< p > g标志故意不会重新匹配替换部分的字符串。你需要做的有点不同。尝试这个:

echo ddayaynightday | sed $':begin\n/day/{ s///; bbegin\n}'

由于BSD Sed的古怪特性,需要嵌入换行符。如果您正在使用GNU Sed,则可以尝试使用。
sed ':begin;/day/{ s///; bbegin }'

在类似sh的shell中,您也可以在引号字符串中插入一个字面换行符。另一方面,对于像这样的短脚本,我发现$'... string with escapes...'结构更美观。对于非常长的脚本,使用-f file可能更好。在这种特定情况下,多个-e命令也可以工作。 - torek
@torek:关于多个“-e”命令的观点很好。我个人不喜欢在引用字符串中嵌入换行符。这通常更难阅读,并且使与其他人共享命令变得更加困难(因为命令通常期望在一行上)。 - Lily Ballard
谢谢,你的第二个在我的OSX上不起作用,它只是打印出输入。 - w00d
@iKid:是的,OS X有BSD sed,这就是为什么你在那里使用第一个版本的原因。 - Lily Ballard

2
以下内容是有效的:
$ echo ddayaynightday | sed ':loop;/day/{s///g;b loop}'
night

根据您的系统,;可能无法用于分隔命令,因此您可以改用以下内容:

echo ddayaynightday | sed -e ':loop' -e '/day/{s///g
                                               b loop}'

说明:

:loop       # Create the label 'loop'
/day/{      # if the pattern space matches 'day'
  s///g     # remove all occurrence of 'day' from the pattern space
  b loop    # go back to the label 'loop'
}

如果命令中的b loop部分未被执行,则会打印模式空间的当前内容,并读取下一行。

这对我没有用。它给出了一个错误:sed: 1: ":loop;/day/{s/day//g;b ...": unused label 'loop;/day/{s/day//g;b loop}' - Graham
@Graham - 你可能正在使用OS X或FreeBSD。这个脚本可以工作,但是你需要像F.J的“解释”一样将它分成单独的行。你只能在GNU sed中将这些命令组合到一行上。 - ghoti
@ Graham,请看我的最近编辑,我包含了一个适合你的版本。 - Andrew Clark
@torek - 在非GNU sed中,分号作为命令分隔符可以起到作用(即在s///gb loop之间),但在其他位置不行,同时在花括号周围需要换行。我认为这是GNU sed相比其他变体的巨大优势之一。这极大地扩展了sed一行命令的能力。 :) - ghoti
谢谢,它们在我的Linux系统上可以工作,但在我的OSX系统上不行(第一个只打印输入,第二个则出现意外的EOF错误)。 - w00d
@iKid - 我也是,完全一样。但像ghoti所说的那样,如果你在“Explanation”部分中空格,它就可以工作。当然,你还需要删除注释,因为sed不理解那样的注释。 - Graham

2

使用bash:

str=ddayaynightday
while true; do tmp=${str//day/}; [[ $tmp = $str ]] && break; str=$tmp; done
echo $str

0

好的,这里有两个在bash中使用的链接:whilestrlen。 使用它们可以实现我的想法:

重复操作直到长度不再改变。

既没有设置标志的方法,也没有编写这样的正则表达式的方法,“替换直到没有更多匹配”。


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