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
(不仅仅是lopy
s 之后的所有sym
)。†
要替换所有 lopy
s(在第一个 sym
之后,然后是另一个 sym
),我们可以捕获 sym
s 和替换端运行代码之间的子字符串,其中正则表达式替换所有 @ 987654334@s
echo "lopy,lopy1,sym,lopy,lopy1,sym" |
perl -pe's sym,\K (.+?) (?=sym) $1 =~ s/lop/lad/gr ex'
为了隔离sym
s 之间的子字符串,我在第一个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 正则表达式替换一次,带有多个替换标志的主要内容,如果未能解决你的问题,请参考以下文章