使用tr命令处理数组

3

我试图使用tr函数来指定两个数组,作为to和from集合。翻译似乎不起作用,或者我没有正确理解它。

由于我是perl的新手,请告诉我是否有做错什么。

我将数组加载为以下形式(我知道这部分是有效的):

open my $fh,'<',"${main_dir}/char_convert" or die "Cannot open allowed conversion file";
my @from_set;
my @to_set;
my @conversion;
while (my $lines = <$fh>) {
  @conversion = split(" ",$lines);
  push @from_set,$conversion[0];
  push @to_set,$conversion[1];
}

#The variable $line holds the data I want converted:
my $statement;
my $result;
$statement = "tr\@from_set\@to_set\$line;"; # Setup the tr command
$result = eval($statement); # perform the conversion
print "$line\n";

结果与输入的数据相同,似乎没有进行转换。我做错了什么?

数据的一个示例部分为“PICAÑA”。

转换文件中的行为“Ñ N”。

因此,我期望得到“PICANA”,但我得到了原始数据。

感谢您的查阅。


欢迎来到本站!请查看tourhow-to-ask page,了解更多关于提问的技巧,以吸引高质量的回答。如果有人提问,您可以编辑您的问题以包含更多信息。例如,包括char_convert文件的示例将使问题更完整。但是,由于您已经有了答案,在这种情况下可能不是必要的 :)。 - cxw
4个回答

5
我假设你选择了使用tr///,因为它比s///更快。如果是这样,请注意每次进行翻译时使用eval会失去效果。唯一能使其更快的方式是使用一次eval,但执行多个转换操作。
除了使编译后的tr///可以多次使用之外,以下方法还修复了Perl语法错误以及代码注入漏洞。
my $from_set = join '', @from_set;
my $to_set   = join '', @to_set;

my $tr = eval("sub { \$_[0] =~ tr/\Q$from_set\E/\Q$to_set\E/r }")
   or die($@);

my $output = $tr->($input);

如果你只需进行一次转译,那么使用s///代替tr///会使你的生活更简单,同时也不会因为无谓的复杂操作而导致程序变慢。

my %map; @map{@from_set} = @to_set;
my $from_set = join '', @from_set;
my $re = qr/([\Q$from_set\E])/;

my $output = $input =~ s/$re/$map{$1}/gr;

谢谢你的帮助,对我很有帮助。正如我所说,我是 Perl 的新手,所以我会犯一些“愚蠢”的错误。但这就是我们学习的方式。 - Graham Brigden
刚刚我修复了一个bug。 - ikegami

3

您的$statement有点不对,正常形式应该是$line =~ tr/a/b/,是这样的吧?所以应该像这样:

my $statement = "\$line =~ tr/\Q@from_set\E/\Q@to_set\E/;"

$line 在计算过程中需要保持为变量,因此被转义为 \$line。将 @from_set@to_set 的内容插入到 $statement 中时,应该进行插值而无需添加 \


1
vlumi,我认为在评估$statement之前,根据Perl版本,您还需要使用 local $"=''。在我的测试系统上,my @a=1,2,3; "@a"的评估结果是"1 2 3",而不是"123",因为默认值 $"" "。请参见perldoc - cxw
1
只要两边的空格位置相同,实际上并不重要,尽管这样更加安全可靠。或许可以将它们合并成标量值,并将其放入评估语句中... - vlumi
@ikegami 已修复,希望可以了吧..? - vlumi
以前从未见过在数组中使用它,但没有理由不这样做。我进行了测试以确保(perl -E'@a=qw(| |); say "\Q@a"')。 - ikegami

2

这里有几个问题,主要与您的tr/../../语句的语法有关。应该像这样:

tr/../../ 语句的语法有误。

$line =~ tr/CHARS/CHARS/;

你将$line放错了位置,并且使用了反斜杠而不是正斜杠(你可以在tr/.../.../语句中使用正斜杠作为分隔符,但请记住它们在双引号字符串中具有特殊含义)。
这似乎可以实现你想要的功能(我已经切换到使用内部DATA文件句柄以方便测试)。
#!/usr/bin/perl

use strict;
use warnings;
use feature 'say';
use utf8;

my @from;
my @to;

while (<DATA>) {
  chomp;
  my @conv = split;
  push @from, $conv[0];
  push @to,   $conv[1];
}

my $line = 'PICAÑA';

my $statement = "\$line =~ tr/@from/@to/";

eval $statement;

say $line;

__DATA__
Ñ N
Ê E

最初的回答:显然,我不知道你在处理哪些字符,但看起来你可能会发现Text::Unidecode很有用。
更新:还值得指出的是,tr/.../.../语句仍然不完全正确(虽然它可以工作)。如果你打印$statement,你会看到它给出了:
$line =~ tr/Ñ Ê/N E/

那个额外的空格是由于Perl在双引号字符串中插入数组元素时会添加一个空格。如果您关心这个问题,可以通过将$"设置为空字符串来解决它。
更新2:经过思考,我认为根本不需要使用数组。为什么不使用标量呢?
my $from = '';
my $to   = '';

# And then, in the loop...

$from .= $conv[0];
$to   .= $conv[1];

# And later still...

my $statement = "\$line =~ tr/$from/$to/";

注入漏洞!使用"tr/\Q$from\E/\Q$to\E/" - ikegami
你可以使用join函数来连接一个列表,例如:my $from = join('', @from_set); - vlumi
@ikegami:在音译中,\Q 没有任何作用。 - Dave Cross
2
  1. 在你的代码中留下一个漏洞,这并不是借口。
  2. 我并没有建议你在音译文字面值中使用它, 我建议你在双引号字符串字面值中使用它。
- ikegami

2

如果你想避免斜杠注入,可以使用 quotemeta,像这样,或者使用 @ikegami 的解决方案:

最初的回答来自 Perl Mongers。

eval sprintf "tr/%s/%s/", map quotemeta, $oldlist, $newlist;

https://www.perlmonks.org/?node_id=445971


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接