sed具有同时和顺序替换

时间:2018-08-16 22:16:21

标签: string bash awk sed

我不确定这是否可以在sed(或awk或任何bash工具)中做我想做的事情:

我想制作一个脚本,用: )替换字符串中的<happy>,用) :替换<sad>。可以使用sed轻松完成此操作:

echo "test : )" | sed 's/: )/<happy>/g'
echo "test ) :" | sed 's/) :/<sad>/g'

不幸的是,有时候我有这样的字符串:

I'm happy : ) : ) : )
I'm sad ) : ) : ) :

在这种情况下,输出应为:

I'm happy <happy> <happy> <happy>
I'm sad <sad> <sad> <sad>

但是通过组合以上两个命令:

echo "I'm happy : ) : ) : )" | sed 's/: )/<happy>/g' | sed 's/) :/<sad>/g'
echo "I'm sad ) : ) : ) :" | sed 's/: )/<happy>/g' | sed 's/) :/<sad>/g'

我会得到:

I'm happy <happy> <happy> <happy>
I'm sad ) <happy> <happy> :

解决此问题的方法是通过从左到右处理字符串来并行进行两个替换。我尝试使用类似这样的方法:sed 's/a/b/g;s/c/d/g',但替换仅是一个接一个地完成,并不能解决问题。

4 个答案:

答案 0 :(得分:5)

使用GNU awk将第三个参数匹配():

$ cat script1.awk
BEGIN {
    map[": )"] = "<happy>"
    map[") :"] = "<sad>"
}
{
    while ( match($0,/(.*)(: \)|\) :)(.*)/,a) ) {
        $0 = a[1] map[a[2]] a[3]
    }
    print
}

$ awk -f script1.awk file
I'm happy <happy> <happy> <happy>
I'm sad <sad> <sad> <sad>

任何awk:

$ cat script2.awk
BEGIN {
    map[": )"] = "<happy>"
    map[") :"] = "<sad>"
}
{
    while ( match($0,/: \)|\) :/) ) {
        $0 = substr($0,1,RSTART-1) map[substr($0,RSTART,RLENGTH)] substr($0,RSTART+RLENGTH)
    }
    print
}

$ awk -f script2.awk file
I'm happy <happy> <happy> <happy>
I'm sad <sad> <sad> <sad>

尽管在这种情况下,两种方法都产生相同的输出,但是第一种方法实际上是从字符串的末尾到前导.*的前部,而第二种方法是从前到后。您可以通过此测试看到这一点:

$ echo ': ) :' | awk -f script1.awk
: <sad>

$ echo ': ) :' | awk -f script2.awk
<happy> :

您可以通过调整对任何awk进行从后到前的通行证,但我认为那并不是您真正想要的。


编辑以从地图构建正则表达式:

$ cat tst.awk
BEGIN {
    map[": )"] = "<happy>"
    map[") :"] = "<sad>"
    for (emoji in map) {
        gsub(/[^^]/,"[&]",emoji)
        gsub(/\^/,"\\^",emoji)
        emojis = (emojis == "" ? "" : emojis "|") emoji
    }
}
{
    while ( match($0,emojis) ) {
        $0 = substr($0,1,RSTART-1) map[substr($0,RSTART,RLENGTH)] substr($0,RSTART+RLENGTH)
    }
    print
}

$ awk -f tst.awk file
I'm happy <happy> <happy> <happy>
I'm sad <sad> <sad> <sad>

答案 1 :(得分:3)

如果有Perl,它可以很好地解决此问题。其e的替换选项使代码更短-对于Perl-来说很整洁。

my %map = (
    ": )" => "<happy>",
    ") :" => "<sad>",
);

while (<>) {
    s/\: \)|\) \:/$map{$&}/ge;
    print;
}

通常的情况-从地图构建正则表达式-在下面的脚本中解决。 Perl的精妙之处在于其正则表达式引擎以|交替形式匹配第一个匹配模式。结果是,替代项需要按照从长到短的顺序进行排序,否则,在下面的示例中,: ))可能与: )相匹配。

$ cat script.pl
#!/usr/bin/perl -w

use strict;

my %map = (
    ": )" => "<happy>",
    ") :" => "<sad>",
    ": |" => "<meh>",
    ": ))" => "<really happy>",
);

my @map_regexes = keys %map;
my @map_regexes_longest_first = reverse sort @map_regexes;
my @quoted_map_regexes = map(quotemeta, @map_regexes_longest_first);
my $map_regex = join("|", @quoted_map_regexes);

while (<>) {
    s/$map_regex/$map{$&}/ge;
    print;
}
$ cat file.txt
I'm happy : ) : ) : )
I'm sad ) : ) : ) :
I'm meh : | : | : |
I'm really happy : )) : )) : ))
$ perl -w script.pl <file.txt
I'm happy <happy> <happy> <happy>
I'm sad <sad> <sad> <sad>
I'm meh <meh> <meh> <meh>
I'm really happy <really happy> <really happy> <really happy>

答案 2 :(得分:2)

对于给定的样本(即处理两个重叠的匹配项),可以使用循环并用sed进行求解

$ cat ip.txt
I am happy : ) : ) : )
I am sad ) : ) : ) :
: ) : ) : )
) : ) : ) :
) : : ) :
: ) ) :

$ # GNU version: sed -E -e ':a s/(^|[^)].): \)/\1<happy>/g; ta' -e 's/\) :/<sad>/g'
$ sed -E -e ':a' -e 's/(^|[^)].): \)/\1<happy>/g' -e 'ta' -e 's/\) :/<sad>/g' ip.txt
I am happy <happy> <happy> <happy>
I am sad <sad> <sad> <sad>
<happy> <happy> <happy>
<sad> <sad> <sad>
<sad> <happy> :
<happy> <sad>
  • -e ':a'标签a
  • s/(^|[^)].): \)/\1<happy>/g: )替换<happy>,只要第二个字符不是)即可
  • 如果替换成功,
  • -e 'ta'分支到标签a-需要循环,因为我们必须检查4个字符才能一次替换2个字符
  • s/\) :/<sad>/g一旦替换了所有快乐的表情符号,我们就可以一次更改所有可悲的表情符号


对于多个映射,这是一个perl解决方案,类似于awk

$ perl -pe 'BEGIN{ $h{": )"}="<happy>"; $h{") :"}="<sad>";
                   $r = join "|", map quotemeta, keys %h; }
            s/$r/$h{$&}/g' ip.txt
I am happy <happy> <happy> <happy>
I am sad <sad> <sad> <sad>
<happy> <happy> <happy>
<sad> <sad> <sad>
<sad> <happy> :
<happy> <sad>
  • $h{": )"}="<happy>"创建键值对的哈希值
  • $r = join "|", map quotemeta, keys %h根据哈希值%h的所有键创建正则表达式替换... map quotemeta将为每个哈希键转义[A-Za-z_0-9]以外的所有字符
  • s/$r/$h{$&}/g搜索并替换

答案 3 :(得分:1)

我们可以通过两个步骤解决此问题:

  1. 确定可替换的字符串,并用定界符标记它们(我将!用作开始和结束,但几乎可以使用任何东西)。
  2. 现在分别替换那些定界的字符串。

以下是实现此方法的sed程序:

#!/bin/sed -f

s/) :\|: )/!&!/g


s/!: )!/<happy>/g
s/!) :!/<sad>/g

有关分隔符的说明:

我们可以为此使用任何定界符,因为我们总是重新匹配并替换我们引入的定界符。在所有sed脚本中并非如此,通常,将\n用作分隔符(如果您正在处理单行)或另一个不太可能的字符(也许{{1} }或\0(如果要处理普通文本)。

我们可以在此脚本中使用任何字符。例如,使用\377a同样有效:

b
#!/bin/sed -f

s/) :\|: )/a&b/g

s/a: )b/<happy>/g
s/a) :b/<sad>/g
$ sed -f ../stackoverflow/51886023.sed <<<$'I\'m happy : ) : ) : )\nI\'m sad ) : ) : ) :'