字符映射转换:tr和y///

除了可用使用s///来替换字符串,Perl还支持y///,它和sed命令的y///作用类似,用于字符映射转换。

Perl中,还支持tr,tr是和y///等价的别名。

tr的语法:

tr/SEARCH/REPLACEMENT/cdsr
y/SEARCH/REPLACEMENT/cdsr

tr或y用于将SEARCH中出现的字符(或指定修饰符c表示未出现的字符)逐字符转换为REPLACEMENT中的字符,根据指定的修饰符不同,转换操作要么是替换(默认是替换),要么是删除(d修饰符)。

tr默认返回替换成功或删除成功的字符数量,如果指定修饰符r,则拷贝一份数据,对数据进行转换并返回,原数据保持不变。

其中:

  • c:取search的补集,将search中未找到的字符全都替换成replacement的最后一个字符
  • d:删除search中出现的字符
  • s:压缩重复字符,仅仅只需要压缩不需要替换时,可将replacement指定为空
  • r:返回的不是替换成功的数量,而是替换成功后的内容,原数据不变,和s///的r修饰符是一样的

y///tr///的斜线分隔符可以替换为其他符号,例如tr### tr%%% tr=== 或括号方式tr{}{} tr()() tr{}[]以及它们的混合方式tr{}// tr()##

tr的基本用法

下面是tr的一些基本用法。

1.tr的映射功能

将小写字母e替换为大写字母E。

my $str = "abcdef";
$str =~ y/e/E/;
say $str;

将小写字母全替换为大写字母。

my $str="abcdef";
$str =~ y/a-z/A-Z/;
say $str;

如果对同一个字母指定不同的映射集,那么第一个映射关系将生效。

my $str="aaa ddd eee fff";
$str =~ tr/aaa/xyz/;  # a被映射为x
say $str;   # 输出xxx ddd eee fff

如果未指定Replacement部分,则拷贝Search部分作为Replacement,相当于不做任何替换,但却执行了tr操作,因此有返回值:

say tr/abcd//;   # 等价于say tr/abcd/abcd/

如果SEARCH部分比REPLACEMENT部分更长,则REPLACEMENT部分以最后一个字符补齐:

tr/abcd/AB/;  # 等价于tr/abcd/ABBB/

2.s修饰符压缩连续字符

使用s修饰符,可将SEARCH中出现的字符进行压缩,然后映射替换或删除。

例如:

my $str = "aabbccdd";
$str =~ tr/a//s;  # 连续的a替换为单个a,结果abbccdd
$str =~ tr/b/b/s; # 连续的b替换为单个b,结果abccdd
$str =~ tr/c/*/s; # 连续的c替换为单个*,结果ab*d

再例如:

my $str="abc    ddd eee    fff";
$str =~ tr/ //s;   # 压缩连续空格,结果:abc ddd eee fff
$str =~ tr/d/x/s;  # 压缩连续字符d,然后替换,结果:abc x eee fff
$str =~ tr/ef/y/s; # 压缩连续e和f,然后替换,结果:abc x y y
say $str;

可以压缩换行符:

my $str1="abc\n\nddd\neee    fff";
$str1 =~ tr/\n //s;  # abc\nddd\neee fff

3.d修饰符删除SEARCH中未参与替换的字符

例如,不指定replacement,search中的字符都被删除。用于直接删除某些字符。

my $str="abc ddd eee fff";
$str =~ y/de//d;    # abc   fff

指定replacement时,将先映射替换,再删除其余未参与替换的字符:

my $str="abc ddd eee fff";
$str =~ y/de/x/d;   # d替换为x,e被删除,结果abc xxx  fff
$str =~ y/abcf/mn/d; # a->m b->n,cf被删除,结果"mn xxx  "

4.c修饰符取补集,将search中未指定的字符全部替换为replacement中的最后一个字符

c修饰符的作用,是将除search中指定字符之外的所有字符,都看作同一类字符。

my $str="aaa bbb ccc ddd";
$str =~ y/ab/xy/c;   # aaaybbbyyyyyyyy

注意上面,除a和b外,其余字符全都替换为y,replacement中的x字符被忽略。

c修饰符的作用,相当于将除了ab字符之外的其余字符,都看作另一种相同字符,然后进行下一步操作。比如上面示例,空格字符和cd字符都可以被看作同一类字符A1 A2 A3,然后它们都被替换为字符y。

如果replacement比search长,则仍然是取replacement的最后一个字符作为替换字符。所以下面的等价:

y/ab/xy/c;
y/ab/zxy/c

因此,replacement部分指定单个替换字符即可。

如果同时指定了s修饰符,则先补集替换,然后再压缩。

my $str="aaa bbb ccc ddd";
$str =~ y/ab/xy/sc;   # 先补集替换,得到aaaybbbyyyyyyyy
                      # 再压缩得到aaaybbby

因此,sc修饰符的效果是:除了search部分指定的字符外,其余字符全部被当成同一类字符,然后压缩。

如果同时指定cd修饰符,则删除所有未在search中的字符,也就是说replacement是多余的:

my $str="aaa bbb c d";
$str =~ y/ab/xy/dc; # 删除所有非ab字符,结果:aaabbb
$str =~ tr/0-9//cd; # 删除所有非数字字符

5.使用r返回替换后的结果

r修饰符使得处理数据前会先拷贝一份数据,然后对副本数据进行操作,所以原始数据会保持不变。

my $str="abcdef";
say $str =~ y/e/E/r;   # abcdEf
say $str;   # abcdef

tr的一些细节

tr不指定操作对象时,默认操作$_。因此,下面是等价的:

$_ =~ tr/a/b/;
tr/a/b/;

tr默认返回被替换或被删除的字符数量,这可以作为获取字符串长度的一种技巧。但需要注意,和length获取字符长度一样,如果被操作的字符串中含有多字节字符,则应当use utf8,否则得到字节数而不是字符数。

say tr///c;  # 输出$_变量的字符数量

my $s="你好";
say $s =~ y///c;  # 6
{
  use utf8;
  say $s =~ y///c; # 2
}

使用r修饰符使得tr将拷贝一份数据,并对副本数据进行替换删除,原数据保持不变。因此,可将r修饰符操作后的数据赋值给变量,且有一些技巧可用:

($STR = $str) =~ tr/a/A/; # str不变,对STR进行转换
$STR = $str =~ tr/a/A/r;  # str不变,拷贝修改后赋值给STR

$STR = $str =~ tr/a/A/r   
            =~ s/:/ -p/r; # tr///r和s///r串联

tr总是在编译期间生成字符映射表,因此,无法在search部分和replacement部分使用变量。如果要使用变量,则应该使用eval来实现:

eval "tr/$oldlist/$newlist/";

tr有两种方式指定字符范围:a-z0-9A-Z,以及[a-z0-9A-Z],前者不使用中括号,在编译期间可以精确知道哪些字符需要生成映射表,后者在编译后仍然是动态的字符类,因此无法对其生成映射表。使用不同的字符范围语法,可能会导致不同的结果。例如,在使用d修饰符删除时:

my $str="abc ddd eee fff";
# $str =~ y/d-f//d;     # e不会被保留,得到"abc   "
# $str =~ y/d-f/e/d;    # e被保留,得到"abc eee  "
# $str =~ y/[d-f]/e/d;  # e不会被保留,得到"abc   "
# $str =~ y/[def]/e/d;  # e不会被保留,得到"abc   "
# $str =~ y/[df]e/e/d;  # e不会被保留,得到"abc   "

除了=~,也可以使用!~操作符,这表示先进行转换,对转换后的结果布尔取反。也就是说,如果执行了转换操作,则返回布尔假,未执行转换操作,则返回布尔真。例如:

$str !~ tr/A//;   # 若$str中有A时返回假,没有A时返回真