Используйте tr с массивами - PullRequest
3 голосов
/ 11 июня 2019

Я пытаюсь использовать функцию 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», но я получаю исходные данные

Спасибо за просмотр

Ответы [ 4 ]

6 голосов
/ 11 июня 2019

Я предполагаю, что вы пошли с 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);

Если, с другой стороны, вы выполняете транслитерацию только один раз, то вы усложняете свою жизнь и просто так замедляете свою программу, используя tr///. Вместо этого используйте s///.

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;
3 голосов
/ 11 июня 2019

Ваш $statement немного отключен, так как нормальная форма будет $line =~ tr/a/b/, верно?Так должно быть так:

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

Значение $line должно оставаться переменной во время оценки, поэтому оно экранируется как \$line.Содержимое @from_set и @to_set следует интерполировать в $statement, поэтому они указываются без \.

2 голосов
/ 11 июня 2019

От Perl Mongers, если вы хотите обезопасить себя от инъекций слеша, вы должны использовать цитату, подобную этой, или использовать решение @ikegami:

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

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

2 голосов
/ 11 июня 2019

Здесь есть несколько проблем.Они в основном соответствуют синтаксису вашего оператора 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/";
...