【问题标题】:Perl tr operator is transliterating based on the variable's name not its valuePerl tr 运算符是根据变量的名称而不是其值进行音译
【发布时间】:2016-05-29 16:29:24
【问题描述】:

我正在使用 Perl 5.16.2 来尝试计算 $_ 字符串中特定分隔符的出现次数。分隔符通过@ARGV 数组传递给我的Perl 程序。我验证它在程序中是正确的。我计算字符串中分隔符数量的指令是:

$dlm_count = tr/$dlm//;

如果我硬编码分隔符,例如$dlm_count = tr/,//; 计数正确。但是当我使用变量 $dlm 时,计数是错误的。我修改了指令说

$dlm_count = tr/$dlm/\t/;

并从如何将制表符插入到字符串中意识到该操作正在替换“$”、“d”、“l”或“m”这四个字符中的任何一个的每个实例" 到 \t — 即构成我的变量名 $dlm 的四个字符中的任何一个。

这是一个说明问题的示例程序:

$_ = "abcdefghij,klm,nopqrstuvwxyz";
my $dlm = ",";
my $dlm_count = tr/$dlm/\t/;
print "The count is $dlm_count\n";
print "The modified string is $_\n";

$_ 字符串中只有两个逗号,但此程序会打印以下内容:

The count is 3  
The modified string is abc      efghij,k                ,nopqrstuvwxyz

为什么$dlm 标记被视为四个字符的文字字符串而不是变量名?

【问题讨论】:

    标签: perl


    【解决方案1】:

    您不能那样使用tr,它不会插入变量。它严格按字符替换运行。所以这个

    $string =~ tr/a$v/123/
    

    将用1替换每个a,用2替换每个$,用3替换每个v。它不是一个正则表达式,而是一个音译。来自perlop

    因为音译表是在编译时构建的,所以 SEARCHLIST 和 REPLACEMENTLIST 都不受双引号插值的影响。这意味着如果要使用变量,则必须使用 eval():

    eval "tr/$oldlist/$newlist/";
    die $@ if $@;
    eval "tr/$oldlist/$newlist/, 1" or die $@;
    

    来自 docs 的上述示例提示了如何计数。对于$dlms 中的$string

    $dlm_count = eval "\$string =~ tr/$dlm//";  
    

    $string 被转义,因此在到达eval 之前不会被插值。你的情况

    $dlm_count = eval "tr/$dlm//";
    

    您还可以使用tr(或正则表达式)以外的工具。例如,字符串在$_

    my $dlm_count = grep { /$dlm/ } split //;
    

    当split 以空字符串模式(//)打破$_ 时,它会返回其中所有字符的列表。然后grep 块针对$dlm 进行测试,因此返回与$_ 中一样多的$dlm 字符列表。由于这是分配给一个标量,$dlm_count 被设置为该列表的长度,这是所有 $dlm 的计数。

    【讨论】:

      【解决方案2】:

      在docs on perlop 'Quote Like Operators' 的部分中,它声明:

      因为音译表是在编译时建立的,所以也不 SEARCHLIST 和 REPLACEMENTLIST 都受到双引号 插值。这意味着如果你想使用变量,你必须 使用 eval():

      【讨论】:

        【解决方案3】:

        正如记录和您发现的那样,tr/// 不会进行插值。简单的解决方案是改用s///。

        my $dlm = ",";
        $_ = "abcdefghij,klm,nopqrstuvwxyz";
        my $dlm_count = s/\Q$dlm/\t/g;
        

        如果音译是在循环中执行的,以下可能会显着加快速度:

        my $dlm = ",";
        my $tr = eval "sub { tr/\Q$dlm\E/\\t/ }";
        for (...) {
           my $dlm_count = $tr->();
           ...
        }
        

        【讨论】:

          【解决方案4】:

          尽管有几个答案暗示了tr/// 的eval() 习语,但没有一个答案涵盖字符串中包含tr 语法字符的情况,例如-(连字符):

          $_ = "abcdefghij,klm,nopqrstuvwxyz";
          
          my $dlm = ",";
          
          my $dlm_count = eval sprintf "tr/%s/%s/", map quotemeta, $dlm, "\t";
          

          但正如其他人所指出的,在 Perl 中有很多方法可以避免 eval() 对字符进行计数,这是另一种方法:

          my $dlm_count = () = m/$dlm/go;
          

          【讨论】:

          • 哇,这是回复的金矿。感谢所有回复的人,我非常感谢。
          猜你喜欢
          • 1970-01-01
          • 2017-11-12
          • 1970-01-01
          • 2015-05-16
          • 1970-01-01
          • 1970-01-01
          • 2017-10-26
          • 2015-02-14
          • 1970-01-01
          相关资源
          最近更新 更多