sed 和 Perl 正则表达式替换一次,带有多个替换标志

Posted

技术标签:

【中文标题】sed 和 Perl 正则表达式替换一次,带有多个替换标志【英文标题】:sed and Perl regexp replaces once, with multiple replacements flag 【发布时间】:2021-11-24 15:23:24 【问题描述】:

我有字符串:

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/$1lad/g'

产量

lopy,lopy1,sym,lad,lopy1,sym

并不是所有的“lopy”都被替换了。我做错了什么?

【问题讨论】:

【参考方案1】:

(.*sym.*?)lopy / (.*sym.+?)lopy 模式几乎相同,.+? 匹配除换行符之外的一个或多个字符,但尽可能少,.*? 匹配零个或多个这样的字符。注意sed 不支持惰性量词,*?sed 中的* 相同。然而,你使用的正则表达式的主要问题是它们匹配sym,然后是它后面的任何文本,然后是lopy,所以当你添加g时,它只是意味着你想找到更多lopy的情况在sym....lopy 之后。而且您的字符串中只有一次这样的事件。

你想在sym之后替换所有lopy,所以你可以使用

perl -pe 's/(?:\G(?!^)|sym).*?\Klopy/lad/g'

请参阅regex demo。 详情

(?:\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(参见this regex demo)。

【讨论】:

【参考方案2】:

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]/ '

如果你想花哨的话,你可以使用lookbehind来只替换第一个sym之后出现的lop

echo "lopy,lopy1,sym,lopy,lopy1,sym" |
perl -pe 's/(?<=sym.0,200)lop/lad/'

可变长度的lookbehind 生成一个警告,并且仅在Perl 5.30+ 中受支持(you can turn it off 和no warnings qw(experimental::vlb));。)

【讨论】:

【参考方案3】:

问题是要替换的lopy(s) 在sym 之后,具有类似sym.*?lopy 的模式,因此全局替换会寻找更多的整个sym+lopy-after -sym(不仅仅是lopys 之后的所有sym)。

要替换所有 lopys(在第一个 sym 之后,然后是另一个 sym),我们可以捕获 syms 和替换端运行代码之间的子字符串,其中正则表达式替换所有 @ 987654334@s

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

为了隔离syms 之间的子字符串,我在第一个sym 之后使用\K,它会丢弃之前的匹配,并在子字符串之后对sym 进行正向前瞻,这不会消耗任何东西. /e 修饰符使替换端被评估为代码。在替换方的正则表达式中,我们需要/r,因为$1 不能更改,并且我们希望正则表达式无论如何都返回。见perlretut。


为了匹配所有的abbbb,我们不能说/ab/g,也不能说/(a)b/g,也不能说/a(b)/g,因为这样会寻找整个ab的所有重复字符串(在开头只找到ab)。

【讨论】:

【参考方案4】:

由于 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.
'

【讨论】:

【参考方案5】:

由于您已尝试使用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 子字符串

【讨论】:

以上是关于sed 和 Perl 正则表达式替换一次,带有多个替换标志的主要内容,如果未能解决你的问题,请参考以下文章

正则表达式用 n替换^ M或删除^ M.

使用 sed/perl/awk 替换第一次出现的匹配文本

使用 sed 或 Perl 来注释 XML 块的正则表达式

sed命令和正则表达式

正则表达式(sedawk)

用正则表达式替换某一区段内的字符,在线等