sed:替换多个模式,但不能使用相同的字符串

时间:2015-04-13 13:28:14

标签: bash sed

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

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)。 我应该提一下,我的真实模式比那更复杂......

谢谢你!

3 个答案:

答案 0 :(得分:11)

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'  

答案 1 :(得分:2)

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.

答案 2 :(得分:2)

如果您的&#34;单词&#34;不包含RE metachars(。*?etc。):

$ 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

以上显然映射了&#34;&#34;到&#34; a&#34;和&#34; foo&#34;去&#34;酒吧&#34;并使用GNU awk进行单词边界。

如果你的话是&#34;&#34;确实包含RE metachars等。然后你需要使用index()而不是基于RE的使用match()的基于字符串的解决方案(注意sed仅支持RE,而不是字符串)。