2016-10-23 24 views
11

Chcę usunąć znaki diakrytyczne w niektórych ciągach znaków. tr/// powinien wykonać zadanie, ale się nie powiedzie (patrz poniżej). Myślałem, że mam problem z kodowaniem/dekodowaniem, ale zauważyłem, że s/// działa tak jak oczekuję. Czy ktoś mógłby wyjaśnić dlaczego?Perl: tr /// nie robi tego, czego się spodziewam, podczas gdy s /// to

Oto przykład wyników uzyskać:

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

Zauważ, że tr/// również zmodyfikowane pierwsze i trzecie znaków napisu, a nie tylko środkowa.

Edytuj: Używam Ubuntu 16.04 z środowiskiem pulp Mate.

Odpowiedz

18

Kiedy nie masz use utf8;, ale przeglądasz kod za pomocą edytora tekstu utf8, nie widzisz go tak, jak perl go widzi. Myślisz, że masz jedną postać w lewej połowie twojej s/// i tr///, ale ponieważ jest to wiele bajtów, perl widzi to jako wiele znaków.

Co sądzisz Perl widzi:

my $str1 = "\xE8\xEE\xFC"; 
my $str2 = $str1; 
$str1 =~ tr/\xEE/i/; 
print "$str1\n"; 
$str2 =~ s/\xEE/i/; 
print "$str2\n"; 

Co Perl faktycznie widzi:

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"; 

Z s///, ponieważ żaden z bohaterów są operatory wyrażeń regularnych, jesteś po prostu robi wyszukiwania podciąg. Szukasz podłańcucha o wielu znakach. I znajdziesz to, ponieważ to samo, co się wydarzyło w twoim s///, dzieje się również w twoich literałach ciągów znaków: znaki, które twoim zdaniem są tam naprawdę, nie są, ale wieloliterowa sekwencja jest.

Z kolei w tr/// wiele znaków nie jest traktowanych jako sekwencja, traktowane są one jako zestaw. Każdy znak (bajt) jest traktowany oddzielnie, gdy zostanie znaleziony. A to nie przynosi oczekiwanych rezultatów, ponieważ zmiana poszczególnych bajtów łańcucha utf8 nigdy nie jest tym, czego potrzebujesz.

Fakt, że można uruchomić proste wyszukiwanie podłańcuchowe zorientowane na ASCII, które nic nie wie o utf8 i uzyskać poprawny wynik na ciągu utf8, jest uważane za dobrą kompatybilność wsteczną funkcji utf8, w przeciwieństwie do innych kodowań, takich jak ucs2/utf16 lub ucs4.


Roztwór powiedzieć perl źródłem jest kodowany za pomocą UTF-8 przez dodanie use utf8;. Będziesz także musiał kodować swoje wyjścia, aby dopasować to, czego oczekuje twój terminal.

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"; 
3

To działa zgodnie z oczekiwaniami dla mnie:

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 umożliwia UTF-8 dla literały w kodzie źródłowego, use open Pragma przełącza STDOUT na UTF-8.

+0

Działa również dla mnie, dziękuję. Każdy pomysł, dlaczego "tr" wydaje się potrzebować tych pragm, podczas gdy 's' nie? – Georg

+4

Chciałem tylko powiedzieć coś o łańcuchach znaków i semantykach ciągów bajtowych, ale zobacz odpowiedź @ Wumpusa, myślę, że wyjaśnia to znacznie lepiej. – zoul

+0

@Zoul, cieszę się, że nie zrobiłeś; Nie ma to nic wspólnego z dwoma wewnętrznymi formatami pamięci. – ikegami