【问题标题】:How to use sed to search and replace a pattern who appears multiple times in the same line?如何使用 sed 搜索和替换同一行中多次出现的模式?
【发布时间】:2019-09-23 08:19:41
【问题描述】:

因为这个问题可能会产生误导,所以这里举个小例子。我有这种文件:

some text
some text @@some-text-KEY-some-other-text@@
text again @@some-text-KEY-some-other-text@@ @@some-text-KEY-some-other-text@@
again @@some-text-KEY-some-other-text-KEY-text@@
some text with KEY @@KEY-some-text@@
blabla @@KEY@@

在此示例中,我想将一对 @@ 中出现的每个 KEY- 替换为 VALUE-。我从这个 sed 命令开始:

sed -i 's/\(@@[^@]*\)KEY-\([^@]*@@\)/\1VALUE-\2/g'

这是它的工作原理:

  1. \(@@[^@]*\):创建由两个@ 和除@ 之外的任何字符组成的第一组...
  2. KEY-: ...直到该行最后一次出现KEY-
  3. \([^@]*@@\):并创建第二组,其中包含除@ 之外的所有字符,直到下一对@

问题是我的命令无法正确处理以下行,因为我的一对 @@ 中有多个 KEY-

again @@some-text-KEY-some-other-text-KEY-text@@

确实,我得到了这个结果:

again @@some-text-KEY-some-other-text-VALUE-text@@

如果我想替换该行中所有出现的KEY-,我必须多次运行我的命令,我宁愿避免这种情况。我也尝试过使用惰性运算符,但问题是一样的。

如何创建可以正确处理我所有文件的正则表达式和 sed 命令?

【问题讨论】:

    标签: regex sed


    【解决方案1】:

    问题相当复杂:您需要替换相同多字符分隔符之间的文本块内所有出现的某些多字符文本。

    解决任务最简单、最安全的方法是使用 Perl:

    perl -i -pe 's/(@@)(.*?)(@@)/$end_delim=$3; "$1" . $2=~s|KEY-|VALUE-|gr . "$end_delim"/ge' file
    

    请参阅online demo

    (@@)(.*?)(@@) 模式将匹配两个相邻 @@ 子字符串之间的字符串,将起始分隔符捕获到第 1 组中,将结束分隔符捕获到第 3 组中,并将其间的所有文本放入第 2 组。由于正则表达式替换会重新设置所有占位符, 临时变量用于保存结束分隔符的值($end_delim=$3),然后,"$1" . $2=~s|KEY-|VALUE-|gr . "$end_delim" 将匹配替换为第一个匹配的 Group 1 中的值(第一个 @@),然后是 Group 2 值,所有KEY- 替换为VALUE-,然后是结束分隔符。

    如果在同一行的匹配之间没有KEY-s,您可以使用带有sed 的分支,方法是将您的命令用:AtA 括起来:

    sed -i ':A; s/\(@@[^@]*\)KEY-\([^@]*@@\)/\1VALUE-\2/g; tA' file
    

    请注意,您错过了\VALUE-\2 中的第一个占位符,它应该是\1VALUE-\2

    online demo

    s="some KEY- text
    some text @@some-text-KEY-some-other-text@@
    text again @@some-text-KEY-some-other-text@@ @@some-text-KEY-some-other-text@@
    again @@some-text-KEY-some-other-text-KEY-text@@
    some text with KEY @@KEY-some-text@@
    blabla @@KEY@@"
    
    sed ':A; s/\(@@[^@]*\)KEY-\([^@]*@@\)/\1VALUE-\2/g; tA' <<< "$s"
    

    输出:

    some KEY- text
    some text @@some-text-VALUE-some-other-text@@
    text again @@some-text-VALUE-some-other-text@@ @@some-text-VALUE-some-other-text@@
    again @@some-text-VALUE-some-other-text-VALUE-text@@
    some text with KEY @@VALUE-some-text@@
    blabla @@KEY@@
    

    更多详情

    sed 允许使用循环和branches。上面代码中的:A 是一个标签,一个特殊的位置标记,可以使用适当的运算符“跳转”。 t 用于创建分支,此“只有在前一个替换命令成功时才会跳转到标签”。因此,一旦模式匹配并发生替换,sed 就会回到原来的位置并重新尝试匹配。如果不成功,sed 继续在字符串中进一步搜索匹配项。所以,tA 的意思是如果搜索和替换操作成功,则返回到标记为A 的位置

    【讨论】:

    • 您能解释一下:AtA 的作用吗?我从没见过他们。
    • @Pierre 我在演示下方添加了详细信息。
    • 如果在一个分隔符的结尾和另一个分隔符的开头之间有一个或多个KEY- 怎么办?
    • @potong 你能问一下OP吗?不知道他们要支持什么场景。
    • @potong 在我的用例中,我不应该替换它。但是,你是对的,这个 sed 命令会。
    【解决方案2】:

    这可能对你有用(GNU sed):

    sed -E 's/@@/\n/g;:a;s/^([^\n]*(\n[^\n]*\n[^\n]*)*\n[^\n]*)KEY-/\1VALUE-/;ta;s/\n/@@/g' file
    

    @@ 转换为换行符。使用循环,将匹配的换行符之间的VAL- 替换为VALUE-。全部完成后用@@'s 替换换行符。

    【讨论】:

      猜你喜欢
      • 2021-02-18
      • 2011-08-16
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2019-09-04
      • 1970-01-01
      • 1970-01-01
      • 2015-06-14
      相关资源
      最近更新 更多