2016-10-23 15 views
11

Tôi muốn xóa các dấu hiệu dấu phụ trong một số chuỗi. tr/// nên thực hiện công việc nhưng không thành công (xem bên dưới). Tôi nghĩ rằng tôi đã có một vấn đề mã hóa/giải mã, nhưng tôi nhận thấy s/// hoạt động như tôi mong đợi. Ai đó có thể giải thích tại sao?Perl: tr /// không làm những gì tôi mong đợi trong khi s /// là

Dưới đây là một ví dụ về kết quả tôi nhận được:

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

Lưu ý rằng tr/// cũng sửa đổi ký tự đầu tiên và thứ ba của chuỗi, không chỉ là một trung gian.

Chỉnh sửa: Tôi sử dụng Ubuntu 16.04 với môi trường máy tính để bàn của Mate.

Trả lời

18

Khi bạn không có use utf8;, nhưng bạn đang xem mã bằng trình chỉnh sửa văn bản utf8, bạn sẽ không thấy mã theo cách mà perl nhìn thấy. Bạn nghĩ rằng bạn có một ký tự đơn ở nửa bên trái của s///tr/// nhưng vì nó là nhiều byte, perl xem nó là nhiều ký tự.

gì bạn nghĩ perl thấy:

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

gì perl thực sự thấy:

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

Với s///, vì không ai trong số các nhân vật được khai thác regexp, bạn chỉ cần làm một tìm kiếm chuỗi con. Bạn đang tìm chuỗi con đa ký tự. Và bạn tìm thấy nó, bởi vì điều tương tự đã xảy ra trong số s/// của bạn cũng xảy ra trong các chuỗi ký tự của bạn: các ký tự bạn nghĩ là có, nhưng chuỗi ký tự nhiều ký tự .

Mặt khác, nhiều ký tự không được coi là một chuỗi, chúng được coi là một bộ. Mỗi ký tự (byte) được xử lý riêng khi nó được tìm thấy. Và điều đó không giúp bạn có được kết quả mong muốn, bởi vì việc thay đổi các byte riêng lẻ của chuỗi utf8 không bao giờ là những gì bạn muốn.

Thực tế là bạn có thể chạy tìm kiếm chuỗi con theo định hướng ASCII không biết gì về utf8 và nhận kết quả đúng trên chuỗi utf8, được xem là tính năng tương thích ngược của utf8, trái ngược với các mã hóa khác như ucs2/utf16 hoặc ucs4.


Giải pháp là cho perl biết nguồn được mã hóa bằng UTF-8 bằng cách thêm use utf8;. Bạn cũng sẽ cần phải mã hóa đầu ra của bạn để phù hợp với những gì thiết bị đầu cuối của bạn mong đợi.

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

này hoạt động như mong đợi cho tôi:

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ü 

Các use utf8 pragma phép UTF-8 cho literals trong mã nguồn, các use open pragma chuyển STDOUT sang UTF-8.

+0

Nó cũng làm việc cho tôi, cảm ơn bạn. Bất kỳ ý tưởng tại sao 'tr' dường như cần những pragmas, trong khi' s' không? – Georg

+4

Tôi chỉ định nói điều gì đó về chuỗi ký tự so với ngữ nghĩa chuỗi byte, nhưng xem câu trả lời của @ Wumpus, tôi nghĩ nó giải thích vấn đề tốt hơn nhiều. – zoul

+0

@zoul, tôi rất vui khi bạn không làm vậy; Điều này không liên quan gì đến hai định dạng lưu trữ nội bộ. – ikegami

Các vấn đề liên quan