2010-06-03 28 views
8

Làm thế nào để các chuỗi perl đại diện trong nội bộ? Mã hóa nào được sử dụng? Làm cách nào để xử lý các mã hóa khác nhau đúng cách?Perl strings internals

Tôi đã sử dụng perl trong một thời gian dài, nhưng nó không bao gồm xử lý chuỗi trong các mã hóa khác nhau và khi gặp phải một vấn đề nhỏ liên quan đến mã hóa tôi thường sử dụng hành động shamanic.

Cho đến thời điểm này, tôi đã nghĩ về chuỗi perl như chuỗi các byte, đã phù hợp khá tốt cho các tác vụ của tôi. Bây giờ tôi cần phải làm một số xử lý của tập tin mã hóa UTF-8 và ở đây bắt đầu gặp rắc rối.

Trước tiên, tôi đọc tập tin vào chuỗi như thế này:

open(my $in, '<', $ARGV[0]) or die "cannot open file $ARGV[0] for reading"; 
binmode($in, ':utf8'); 

my $contents; 

{ 
    local $/; 
    $contents = <$in>; 
} 

close($in); 

sau đó chỉ cần in nó:

print $contents; 

Và tôi nhận được hai điều: một cảnh báo Wide character in print at <scriptname> line <n> và rác trong giao diện điều khiển. Vì vậy, tôi có thể kết luận rằng chuỗi perl có khái niệm "ký tự" có thể là "rộng" hay không, nhưng khi in các ký tự "rộng" này được biểu diễn trong bảng điều khiển dưới dạng nhiều byte, không phải là "ký tự" đơn. (Tôi tự hỏi tại sao tất cả kinh nghiệm trước đó của tôi với các tệp nhị phân hoạt động khá như thế nào tôi mong đợi nó hoạt động mà không có bất kỳ vấn đề "nhân vật" nào).

Tại sao sau đó tôi thấy rác trong bảng điều khiển? Nếu perl lưu trữ chuỗi như ký tự trong một số mã hóa đã biết, tôi không nghĩ rằng có một vấn đề lớn để tìm ra bảng điều khiển mã hóa và in văn bản đúng cách. (Tôi sử dụng Windows, BTW).

Nếu perl lưu trữ chuỗi dưới dạng chuỗi ký tự có độ rộng thay đổi (ví dụ: sử dụng cùng một mã UTF-8), tại sao nó được thực hiện theo cách này? Từ chuỗi xử lý trải nghiệm C của tôi là PAIN.

Cập nhật.

Tôi sử dụng hai máy tính để thử nghiệm, một máy chạy Windows 7 x64 với gói ngôn ngữ tiếng Anh được cài đặt, nhưng với cài đặt khu vực của Nga (vì vậy tôi có cp866 là mã OEM và cp1251 là ANSI) với ActivePerl 5.10.1 x64; người khác chạy Windows XP 32 bit bản địa hóa tiếng Nga với Cygwin Perl 5.10.0.

Nhờ liên kết, giờ đây tôi đã hiểu rõ hơn về những gì đang diễn ra và cách thức thực hiện.

Trả lời

4

Đặt utf8 trước khi đọc từ tệp là tốt, nó tự động giải mã byte thành mã hóa nội bộ. (Mà cũng là UTF-8 nhưng bạn không cần phải biết, và không nên dựa vào.)

Trước khi in, bạn cần phải mã hóa các ký tự trở lại thành byte.

use Encode; 
utf8::encode($contents); 

Ngoài ra còn có hai dạng mã hóa đối số, cho các mã hóa khác hơn unicode. (Câu đó vang quá nhiều, phải không?)

Đây là một tham chiếu tốt. (Đã có được nhiều hơn, nhưng đó là bài viết đầu tiên của tôi.) Kiểm tra perlunitut quá, và bài viết unicode trên Joel trên phần mềm.

http://www.ahinea.com/en/tech/perl-unicode-struggle.html

Oh, và nó phải sử dụng dây đa byte, bởi vì nếu không nó chỉ là không unicode.

+0

Bằng các chuỗi nhiều byte, tôi có nghĩa là mã hóa độ rộng biến đổi. – n0rd

+0

Dù sao tôi không hiểu tại sao tôi phải chuyển đổi một cách rõ ràng: Tôi đã chỉ định mã hóa dữ liệu đầu vào tại sao tôi phải thực hiện một số bước bổ sung? – n0rd

+2

Bạn đã chỉ định mã hóa đầu vào. Bạn làm công cụ của bạn. Sau đó, bạn chỉ định mã hóa đầu ra của mình. Các bài viết tôi gọi là giải thích tốt hơn, tôi nên suy nghĩ. – dylan

2

Bạn nên đề cập đến phiên bản Windows và Perl thực sự của bạn vì điều này thực sự phụ thuộc vào các phiên bản đã sử dụng và gói ngôn ngữ đã cài đặt của bạn.
Nếu không có một cái nhìn tại hướng dẫn PerlUnicode đầu tiên -

Perl sử dụng ký tự một cách logic toàn để đại diện cho chuỗi nội bộ.

nó sẽ xác nhận báo cáo của bạn.

Windows không cài đặt đầy đủ tất cả các ký tự UTF8- vì vậy đây có thể là lý do cho sự cố của bạn. Bạn có thể cần cài đặt gói ngôn ngữ bổ sung.

+0

Câu áp chót của bạn không có ý nghĩa gì cả. Bạn dường như đề cập đến phông chữ, nhưng điều này không có gì để làm với mã hóa. – daxim

4

Chuỗi Perl được lưu trữ nội bộ trong một trong hai mã hóa, hoặc mã hóa thuần 8 byte theo định hướng byte hoặc UTF-8. Đối với khả năng so sánh ngược, giả định là tất cả các I/O và các chuỗi đều được mã hóa riêng, trừ khi được quy định khác. Mã hóa gốc thường là ASCII 8 bit, nhưng điều này có thể được thay đổi với use locale.

Trong mẫu của bạn, bạn gọi binmode trên tay cầm đầu vào của bạn thay đổi nó để sử dụng :utf8 ngữ nghĩa. Một hiệu ứng của điều này là tất cả các chuỗi được đọc từ tay cầm này sẽ được mã hóa dưới dạng UTF-8. print ghi theo STDOUT theo mặc định và STDOUT mặc định để mong đợi các ký tự được mã hóa gốc.

Perl trong nỗ lực làm điều đúng sẽ cho phép chuỗi UTF-8 được gửi đến đầu ra được mã hóa gốc, nhưng nếu không có mã hóa được đính kèm với tay cầm đó thì phải đoán cách xuất nhiều byte nhân vật và nó gần như chắc chắn sẽ đoán sai. Đó là ý nghĩa của cảnh báo, một ký tự nhiều byte được gửi tới một luồng chỉ mong đợi các ký tự byte đơn và kết quả là nhân vật đó có thể đã bị hỏng trong bản dịch.

Tùy thuộc vào những gì bạn muốn hoàn thành, bạn có thể sử dụng mô-đun mã hóa được dylan đề cập để chuyển đổi dữ liệu UTF-8 thành bộ ký tự byte đơn có thể được in an toàn hoặc nếu bạn biết rằng bất kỳ thứ gì được gắn vào STDOUT UTF-8 bạn có thể sử dụng binmode(STDOUT, ':utf8'); để thông báo cho Perl rằng bạn muốn bất kỳ dữ liệu nào được gửi đến STDOUT sẽ được gửi dưới dạng UTF-8.

+0

Nếu mã hóa defualt là 8 bit ASCII (hoặc bất kỳ mã hóa 8 bit khác), tại sao Perl in chuỗi UTF-8 làm byte thô (tức là in hai ký tự để điều khiển cho mỗi ký tự cyrillic trong chuỗi in) thay vì in kết quả chuyển mã mã hóa đó có cùng số lượng ký tự giống như trong chuỗi gốc không? – n0rd

+1

@ n0rd một chuỗi UTF-8 không phải là byte từ góc nhìn perl, đó là các ký tự. Một kết quả lẻ của IIRC này là khi được in đến một tay cầm không mã hóa được xác định, nó sẽ cắt ngắn các điểm mã Unicode lớn hơn 255 thành chỉ 8 bit thấp hơn. –