【问题标题】:Replace multiple patterns, but not with the same string替换多个模式,但不能使用相同的字符串
【发布时间】:2015-06-18 20:01:36
【问题描述】:

是否可以在同一命令中将乘法模式更改为不同的值? 假设我有

A B C D ABC

我想将每个 A 更改为 1,每个 B 更改为 2,每个 C 更改为 3

所以输出将是

1 2 3 D 123

因为我有 3 种模式要更改,所以我想避免单独替换它们。 我以为会有类似的东西

sed -r s/'(A|B|C)'/(1|2|3)/ 

当然,这只是将 A 或 B 或 C 替换为 (1|2|3)。 我应该提一下,我的真实模式比这更复杂......

谢谢!

【问题讨论】:

  • 为什么不sed 's/A/1/g;s/B/2/g;s/C/3/g' file
  • 如果是单个字母,您可以使用 tr tr 'ABC' '123'
  • 什么是(未写入的)约束来避免多个s///,尤其是在像@anubhava 问这样的复杂模式上?
  • 问题与副本的链接不完全相同。链接是这种情况的一个子案例,只有一些特定的简单模式可以用 uniq 新模式替换,其中这个问题在搜索和替换模式中更常见
  • 如果您需要“单词”,您应该使用发布一个使用“单词”的示例,而不仅仅是字母,因为字母更简单(tr)以及处理“单词”的正确方法实际上取决于“单词”对您意味着什么和/或“单词”之间的分隔符可以是什么。正如现在所写的那样,您的问题极有可能产生一个适用于您发布的输入的解决方案,但稍后在针对某些不同的输入运行时会失败(可能悄悄地和/或神秘地和/或灾难性地)。

标签: bash sed


【解决方案1】:

轻松在sed:

sed 's/WORD1/NEW_WORD1/g;s/WORD2/NEW_WORD2/g;s/WORD3/NEW_WORD3/g'

您可以在同一行中用; 分隔多个命令


更新

可能这太容易了。 NeronLeVelu 指出上述命令可能会导致不需要的结果,因为第二次替换甚至可能触及第一次替换的结果(依此类推)。

如果您关心这一点,您可以使用t 命令来避免这种副作用。 t 命令分支到脚本的末尾,但前提是确实发生了替换:

sed 's/WORD1/NEW_WORD1/g;t;s/WORD2/NEW_WORD2/g;t;s/WORD3/NEW_WORD3/g'  

【讨论】:

  • 假设以下搜索模式中没有模式匹配(例如:A -> BABY 比 B -> UNWANTED)
  • 我不明白你。你能详细说明一下吗?
  • 我认为@NeronLeVelu 的意思是,如果较早的替换 results 与后来替换的 regex 匹配,您将得到不希望的双重替换。
  • 好的,是的,这可能发生。我们可以使用t 命令来规避这个问题。让我补充一下。
  • @hek2mgl 完全正确。由于顺序变化而不是并行变化(OR 做什么)。现在,我确信我们的解决方案适用于 99.9% 的情况,所以这不是真正的问题
【解决方案2】:

如果您的“单词”不包含 RE 元字符(. * ? 等),这将起作用:

$ cat file
there is the problem when the foo is closed

$ cat tst.awk
BEGIN {
    split("the a foo bar",tmp)
    for (i=1;i in tmp;i+=2) {
        old = (i>1 ? old "|" : "\\<(") tmp[i]
        map[tmp[i]] = tmp[i+1]
    }
    old = old ")\\>"
}
{
    head = ""
    tail = $0
    while ( match(tail,old) ) {
        head = head substr(tail,1,RSTART-1) map[substr(tail,RSTART,RLENGTH)]
        tail = substr(tail,RSTART+RLENGTH)
    }
    print head tail
}

$ awk -f tst.awk file
there is a problem when a bar is closed

上面显然将“the”映射到“a”,将“foo”映射到“bar”,并使用 GNU awk 作为单词边界。

如果您的“单词”确实包含 RE 元字符等,那么您需要使用 index() 的基于字符串的解决方案,而不是使用 match() 的基于 RE 的解决方案(请注意,sed 仅支持 RE,不支持字符串)。

【讨论】:

  • 有趣的例子! :) 即使单词包含元字符也可以使其工作,我们可以预处理搜索词并转义元字符。
  • 还有“何必呢?”参数,因为确实存在对字符串进行操作的工具:-)。需要考虑的是,这通常出现在 sed 's/search/replace/' 的上下文中,因为 sed 无法处理字符串,所以不仅要考虑在搜索位置(分隔符和 RE 元字符)需要转义的内容,还要考虑替换位置(分隔符和捕获组扩展&amp;\&lt;digit&gt;)。最简单的情况可能是只解决对 awk match() 的搜索,因为它不关心分隔符,您可以使用 substr() 按原样替换匹配的字符串。
  • 多年来我没有问过任何问题! :) 好的!会准备的。 (首先我需要尝试)。我希望我不会得到大量的反对票!嗨
  • 它确实让我思考,我认为我反对这种方法的最大原因是它对上下文非常敏感。您需要转义的字符非常依赖于您使用的工具、您提供的选项、您使用的是 BRE 还是 ERE 或其他东西等等。我认为如果有任何方法能够成功,它会[] 中的每个 [RE 元] 字符,例如,+ 变为 [+],它总是一个字面字符,而不是 \+,它有时是一个 RE 元字符,但我感觉也会有缺点。我想要的只是一个 string :-).
  • ^^^ 可能是明天。得到了喝啤酒的邀请......:D
【解决方案3】:

在 Perl 中很容易:

perl -pe '%h = (A => 1, B => 2, C => 3); s/(A|B|C)/$h{$1}/g'

如果您使用更复杂的模式,请在备选列表中将更具体的模式放在更通用的模式之前。按长度排序可能就足够了:

perl -pe 'BEGIN { %h = (A => 1, AA => 2, AAA => 3);
              $re = join "|", sort { length $b <=> length $a } keys %h; }
          s/($re)/$h{$1}/g'

要添加单词或行边界,只需将模式更改为

/\b($re)\b/
# or
/^($re)$/
# resp.

【讨论】:

  • 在没有单词边界的情况下不太可能有用(例如,尝试将字符串 there is the problem 中的 the 替换为 a)。您需要列出要查找的单词两次也不是很好 - 一次在创建映射时,然后再次在 s// 中。
  • @EdMorton:您通常可以使用join '|', sort { length $b &lt;=&gt; length $a } keys %h 省略第二个列表。您也可以map "\\b$_\\b"\b($re)\b 添加单词边界。
  • 您介意编辑答案以将其作为替代的完整解决方案吗?
  • 我喜欢按长度排序的想法。顺便说一句 - 以前从未想到过,非常有趣的方法可以显着简化该问题的解决方案的编码!
  • @EdMorton 我觉得这很有趣,我听说 按长度排序 建议一天 2 次。 (以前从未有过)。也喜欢。然而,这个概念也来自flex 文件,您可以在其中定义最长的模式,等等。
猜你喜欢
  • 2020-08-31
  • 2016-05-18
  • 1970-01-01
  • 2022-12-15
  • 1970-01-01
  • 1970-01-01
  • 2014-12-17
  • 1970-01-01
相关资源
最近更新 更多