Perl: tr / // не делает то, что я ожидаю, тогда как s/ / /
Я хочу удалить диакритические знаки в некоторых строках. tr///
должны делать работу, но не удается (см. ниже). Я думал, что у меня проблема с кодированием/декодированием, но я заметил s///
работает так, как я ожидаю. Может кто-нибудь объяснить, почему?
вот пример результатов, которые я получаю:
my $str1 = 'èîü';
my $str2 = $str1;
$str1 =~ tr/î/i/;
print "$str1n"; # => i�iii�
$str2 =~ s/î/i/;
print "$str2n"; # => èiü
отметим, что tr///
также изменен первый и третий символы строки, а не только средний.
Edit: я использую Ubuntu 16.04 с Mate desktop окружающая среда.
2 ответов
если у вас нет use utf8;
, но вы просматриваете код с помощью текстового редактора utf8, вы не видите его так, как видит perl. Вы думаете, что один символ в левой части s///
и tr///
но поскольку это несколько байтов, perl видит его как несколько символов.
что вы думаете, perl видит:
my $str1 = "\xE8\xEE\xFC";
my $str2 = $str1;
$str1 =~ tr/\xEE/i/;
print "$str1\n";
$str2 =~ s/\xEE/i/;
print "$str2\n";
что perl на самом деле видит:
my $str1 = "\xC3\xA8\xC3\xAE\xC3\xBC";
my $str2 = $str1;
$str1 =~ tr/\xC3\xAE/i/;
print "$str1\n";
$str2 =~ s/\xC3\xAE/i/;
print "$str2\n";
С s///
, поскольку ни один из символов не является операторами регулярного выражения, вы просто выполнение поиска подстроки. Вы ищете многосимвольную подстроку. И вы найдете его, потому что то же самое произошло в вашем s///
также происходит в ваших строковых литералах: символы, которые вы думаете, там действительно нет, но многозначная последовательность и.
на tr///
С другой стороны, несколько символов не рассматриваются как последовательность, они рассматриваются как набор. Каждый символ (байт) обрабатывается отдельно, когда он найден. И это не дает вам желаемых результатов, потому что изменение отдельных байтов строки utf8 никогда не является тем, что вы хотите.
тот факт, что вы можете запустить простой ASCII-ориентированный поиск подстроки, который ничего не знает о utf8, и получить правильный результат в строке utf8, считается хорошей функцией обратной совместимости utf8, в отличие от других кодировок, таких как ucs2/utf16 или ucs4.
решение состоит в том, чтобы сообщить perl, что источник закодирован с помощью UTF-8, добавив use utf8;
. Вам также нужно будет кодировать свои выходы, чтобы соответствовать ожиданиям вашего терминала.
use utf8; # The source is encoded using UTF-8.
use open ':std', ':encoding(UTF-8)'; # The terminal provides/expects UTF-8.
my $str1 = 'èîü';
my $str2 = $str1;
$str1 =~ tr/î/i/;
print "$str1\n";
$str2 =~ s/î/i/;
print "$str2\n";
это работает, как ожидалось для меня:
use v5.10;
use utf8;
use open qw/:std :utf8/;
my $str1 = 'èîü';
my $str2 = $str1;
$str1 =~ tr/î/i/;
say $str1; # èiü
$str2 =~ s/î/i/;
say $str2; # èiü
на use utf8
pragma включает UTF-8 для литералов в исходном коде,use open
pragma переключает STDOUT на UTF-8.