我不确定这是否可以在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'
,但替换仅是一个接一个地完成,并不能解决问题。
答案 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)
我们可以通过两个步骤解决此问题:
!
用作开始和结束,但几乎可以使用任何东西)。以下是实现此方法的sed程序:
#!/bin/sed -f
s/) :\|: )/!&!/g
s/!: )!/<happy>/g
s/!) :!/<sad>/g
有关分隔符的说明:
我们可以为此使用任何定界符,因为我们总是重新匹配并替换我们引入的定界符。在所有sed脚本中并非如此,通常,将\n
用作分隔符(如果您正在处理单行)或另一个不太可能的字符(也许{{1} }或\0
(如果要处理普通文本)。
我们可以在此脚本中使用任何字符。例如,使用\377
和a
同样有效:
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 ) : ) : ) :'