sed和Perl正则表达式替换一次,使用多个替换标志。

3

我有一个字符串:

lopy,lopy1,sym,lopy,lopy1,sym"

我希望这行内容变成:

lopy,lopy1,sym,lady,lady1,sym

这意味着字符串 sym 后面的所有 "lad" 都应该被替换。因此我运行了:
echo "lopy,lopy1,sym,lopy,lopy1,sym" | sed -r 's/(.*sym.*?)lopy/\1lad/g'

我理解为:

lopy,lopy1,sym,lopy,lad1,sym

使用 Perl 并不一定更好:

echo "lopy,lopy1,sym,lopy,lopy1,sym" | perl -pe 's/(.*sym.+?)lopy/${1}lad/g'

产出
lopy,lopy1,sym,lad,lopy1,sym

并非所有的“lopy”都被替换了。我做错了什么?
5个回答

2

既然OP提到了sed,我在这里添加awk程序。与sed相比,这可能是更好的选择。请使用示例中显示的样本尝试以下awk程序。

echo "lopy,lopy1,sym,lopy,lopy1,sym" | 
awk -F',sym,' '
{
  first=$1
  $1=""
  sub(/^[[:space:]]+/,"")
  gsub(/lop/,"lad")
  $0=first FS $0
}
1
'

说明:为上述内容添加详细解释。

echo "lopy,lopy1,sym,lopy,lopy1,sym" |  ##Printing values and sending as standard output to awk program as an input.
awk -F',sym,' '                         ##Making ,sym, as a field separator here.
{
  first=$1                              ##Creating first which has $1 of current line in it.
  $1=""                                 ##Nullifying $1 here.
  sub(/^[[:space:]]+/,"")               ##Substituting initial space in current line here.
  gsub(/lop/,"lad")                     ##Globally substituting lop with lad in rest of line.
  $0=first FS $0                        ##Adding first FS to rest of edited line here.
}
1                                       ##Printing edited/non-edited line value here.
'

2
(.*sym.*?)lopy”和“(.*sym.+?)lopy”模式几乎相同,“.+?”匹配一个或多个除了换行符以外的字符,但尽可能少,而“.*?”则匹配零个或多个这样的字符。需要注意的是,“sed”不支持惰性量词,“*?”在“sed”中与“*”相同。但是,您使用的正则表达式的主要问题是它们匹配“sym”,然后是其后的任何文本,然后是“lopy”,因此当您添加了“g”时,它只意味着您想要在“sym...lopy”之后查找更多的“lopy”实例。而且在您的字符串中只有一次这样的出现。
您想要替换所有“sym”之后的“lopy”,因此可以使用:
perl -pe 's/(?:\G(?!^)|sym).*?\Klopy/lad/g'

请查看正则表达式演示详情如下:

  • (?:\G(?!^)|sym) - 匹配sym或前一个匹配的结尾(\G(?!^))
  • .*? - 匹配除换行符以外的任意字符,最少次数
  • \K - 匹配重置运算符,丢弃到目前为止匹配的所有文本
  • lopy - 一个lopy字符串。

请查看在线演示:

#!/bin/bash
echo "lopy,lopy1,sym,lopy,lopy1,sym" | perl -pe 's/(?:\G(?!^)|sym).*?\Klopy/lad/g'
# => lopy,lopy1,sym,lad,lad1,sym

如果值始终以逗号分隔,您可以将.*?替换为,(?:\G(?!^)|sym),\Klopy(请参见此正则表达式演示)。

2
问题在于需要替换的lopy位于sym之后,并且匹配模式为sym.*?lopy,因此全局替换会查找更多的整个sym+lopy-在-sym之后的内容(不仅是这一个sym之后的所有lopy)。

要替换所有lopy(第一个sym之后,紧接着另一个sym),我们可以捕获sym之间的子字符串,在替换侧运行代码,其中正则表达式替换所有lopy

echo "lopy,lopy1,sym,lopy,lopy1,sym" | 
    perl -pe's{ sym,\K (.+?) (?=sym) }{ $1 =~ s/lop/lad/gr }ex'

为了从sym之间隔离子串,我在第一个sym后使用\K来删除其之前的匹配,并使用正向先行匹配sym之后的子串,这不会消耗任何东西。替换方面使用/e修饰符使得替换部分被评估为代码。在替换方面的正则表达式中,我们需要使用/r,因为$1无法更改,而我们仍然希望该正则表达式返回。请参见perlretut
要匹配所有的abbbb,我们不能使用/ab/g/(a)b/g/a(b)/g,因为这将查找字符串中整个ab的所有重复出现(并仅在开头找到ab)。

1

sed 不支持非贪婪通配符。但你的 Perl 脚本还因为其他原因而失败了;你说“匹配所有出现的”,但是你指定了一个只能匹配一次的正则表达式。

一个常见的简单解决方案是将字符串拆分,然后仅在匹配后替换:

echo "lopy,lopy1,sym,lopy,lopy1,sym" |
perl -pe 'if (@x = /^(.*?sym,)(.*)/) { $x[1] =~ s/lop/lad/g; s/.*/$x[0]$x[1]/ }'

如果你想更加高级一些,你可以使用回顾后发现来仅替换第一个 sym 后出现的 lop
echo "lopy,lopy1,sym,lopy,lopy1,sym" |
perl -pe 's/(?<=sym.{0,200})lop/lad/'

变长回溯预测会生成警告,只有在Perl 5.30+中支持(你可以使用no warnings qw(experimental::vlb));关闭它)。

0

既然您已经展示了一个尝试使用sed命令并使用sed标签的示例,这里提供一种基于sed循环的解决方案:

sed -E -e ':a' -e 's~(sym,.*)lopy~\1lady~g; ta' file

lopy,lopy1,sym,lady,lady1,sym"

解释:

  • :a在匹配sym,.*模式之前设置标签a
  • ta在进行替换后将模式匹配跳回标签a

s命令没有匹配到任何内容时,即sym,后没有lopy子字符串时,此循环停止。


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