Perl: tr/// не делает того, что я ожидаю, тогда как s///

Я хочу удалить диакритические знаки в некоторых строках. tr/// должен выполнить задание, но не работает (см. ниже). Я думал, что у меня проблема с кодировкой/декодированием, но заметил, что s/// работает так, как я ожидал. Может кто-нибудь объяснить, почему?

Вот пример результатов, которые я получаю:

my $str1 = 'èîü';
my $str2 = $str1;
$str1 =~ tr/î/i/;
print "$str1\n"; # => i�iii�
$str2 =~ s/î/i/;
print "$str2\n"; # => èiü

Обратите внимание, что tr/// также изменяет первый и третий символы строки, а не только средний.

Изменить: я использую Ubuntu 16.04 со средой рабочего стола Mate.


person Georg    schedule 23.10.2016    source источник


Ответы (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";
person Community    schedule 23.10.2016

Это работает, как и ожидалось для меня:

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 включает UTF-8 для литералов в исходном коде, прагма use open переключает STDOUT на UTF-8.

person zoul    schedule 23.10.2016
comment
У меня тоже работает, спасибо. Есть идеи, почему tr нужны эти прагмы, а s нет? - person Georg; 23.10.2016
comment
Я просто собирался сказать кое-что о семантике строк символов и строк байтов, но посмотрите ответ @Wumpus, я думаю, он намного лучше объясняет проблему. - person zoul; 23.10.2016
comment
@zoul, я рад, что ты этого не сделал; Это никак не связано с двумя форматами внутренней памяти. - person ikegami; 24.10.2016
comment
Я не знаю о внутренней памяти, но, как мне кажется, ошибка была вызвана тем, что программист рассматривал строку как набор символов UTF-8, а Perl (без прагм Unicode) видел их как строки ASCII — или сбор байтов. Вот что я имел в виду под семантикой символов и байтовых строк. - person zoul; 24.10.2016