如果在两个其他关键字之间出现,则替换首次出现的关键字

时间:2015-12-12 15:44:57

标签: regex sed

假设一个包含三个不同关键字的字符串(缩写为" kw")。

> echo "foo kw1 bar kw2 baz kw3"

我想替换" kw2"与其他一些字符串,说" qux"。我在以下 sed 命令中缺少什么?

> echo "foo kw1 bar kw2 baz kw3" | sed 's/\(kw1\)kw2\(.*kw3\)/\1qux\2/'
# Current output: foo kw1 bar kw2 baz kw3
# Desired output: foo kw1 bar qux baz kw3 

编辑1 : 实际上,如果有多次出现,我想只更换第一次出现的kw2。如何修改建议的 sed 命令?

> echo "foo kw1 bar kw2 baz kw3 kw2 baz kw3" | sed ...
# Desired output: foo kw1 bar qux baz kw3 kw2 baz kw3

编辑2 : 为清楚起见:如果有多次出现kw2,则要替换的事件是在字符串中某处前面有kw1的第一个事件(即,kw1不一定与kw2相邻)并且后跟kw3(即,kw2不一定相邻)至kw3)。

> echo "foo kw2 bar kw1 bar kw2 baz kw3" | sed ...
# Desired output: foo kw2 bar kw1 bar qux baz kw3

4 个答案:

答案 0 :(得分:1)

使用sed和多个字符分隔符(作为kw2)无法完成您的要求,因为不支持延迟修饰符或前瞻。

<强> PERL

perl中,您只需使用延迟修饰符*?

echo "foo kw1 bar kw2 baz kw3 foo kw2 bar kw3" | perl -pe 's/(\bkw1\b.*?)\bkw2\b(.*?\bkw3\b)/\1qux\2/'
# output -> 'foo kw1 bar qux baz kw3 foo kw2 bar kw3'

<强> SED

sed中的问题只有在分隔符为单个字符时才能解决。

然后给出一个未包含在输入字符串中的单个字符(实际上每个贪婪部分.*<delimiter>的分隔符一个字符),您可以使用此方法:

echo "foo kw1 bar kw2 baz kw3 foo kw2 bar kw3" | sed 's/\bkw2\b/~/g' | sed 's/\bkw3\b/#/g' | sed -E 's/(\bkw1\b[^~]*)~([^#]*#)/\1qux\2/' | sed 's/~/kw2/g' | sed 's/#/kw3/g'
# output -> 'foo kw1 bar qux baz kw3 foo kw2 bar kw3'

注意:我在主表达式中使用了正则表达式sed -E的扩展版本以避免转义圆括号

<强>算法

  • s/\bkw2\b/~/g:将每个kw2(非子字符串)替换为~(假设输入字符串中没有其他~
  • s/\bkw3\b/#/g:将每个kw3(非子字符串)替换为#(假设输入字符串中没有其他#
  • s/(kw1[^~]*)~([^#]*#)/\1qux\2/:使用否定的char类[^~]*[^#]*仅选择第一个kw1(第一个代字号)和第一个kw3(第一个哈希值)。
  • s/~/kw2/g:恢复kw2
  • s/#/kw3/g:恢复kw3

答案 1 :(得分:0)

您需要将\(kw1\)更改为\(kw1.*\),以便匹配kw1kw2之间的字符:

$ echo "foo kw1 bar kw2 baz kw3" | sed 's/\(kw1.*\)kw2\(.*kw3\)/\1qux\2/'
foo kw1 bar qux baz kw3

答案 2 :(得分:0)

这可能适合你(GNU sed):

sed -r 's/kw1/&\n/;T;:a;s/\n$//;t;s/\nbar/qux/;t;s/\n(.)/\1\n/;ta' file

这会在kw1之后插入一个换行符,然后一次沿着一个字符碰撞,直到找到并替换目标字符串。

答案 3 :(得分:0)

正如所讨论的here,以下 perl 命令提供了正确的替换:

perl -ple 's/(kw1.*?)kw2(.*?kw3)/$1qux$2/'

可能有其他选择,但这个命令似乎是我遇到的最直接的。

示例:

> echo "foo kw2 bar kw1 bar kw2 baz kw3" | perl -ple 's/(kw1.*?)kw2(.*?kw3)/$1qux$2/'
# foo kw2 bar kw1 bar qux baz kw3

> echo "foo kw1 bar kw2 baz kw3 kw2 baz kw3" | perl -ple 's/(kw1.*?)kw2(.*?kw3)/$1qux$2/'
# foo kw1 bar qux baz kw3 kw2 baz kw3