Die Verarbeitung von Unicode-Text erfolgt in zwei Stufen. Die erste ist "wie kann ich ihn eingeben und ausgeben, ohne dass Informationen verloren gehen". Die zweite lautet: "Wie behandle ich den Text entsprechend den Konventionen der jeweiligen Sprache?
tchrist's Beitrag deckt beides ab, aber der zweite Teil ist es, aus dem 99% des Textes in seinem Beitrag stammen. Die meisten Programme handhaben E/A nicht einmal richtig, daher ist es wichtig, das zu verstehen, bevor man sich überhaupt Gedanken über Normalisierung und Kollationierung macht.
Dieser Beitrag zielt darauf ab, dieses erste Problem zu lösen
Wenn Sie Daten in Perl einlesen, ist es egal, in welcher Kodierung sie vorliegen. Es wird Speicher zugewiesen und die Bytes werden dort verstaut. Wenn Sie sagen print $str
werden diese Bytes einfach an Ihr Terminal weitergegeben, das wahrscheinlich so eingestellt ist, dass es davon ausgeht, dass alles, was in das Terminal geschrieben wird, UTF-8 ist, und Ihr Text wird angezeigt.
Wunderbar.
Nur ist das nicht der Fall. Wenn Sie versuchen, die Daten als Text zu behandeln, werden Sie feststellen, dass etwas Schlimmes passiert. Sie brauchen nicht weiter zu gehen als length
um zu sehen, dass das, was Perl über Ihre Zeichenkette denkt, und das, was Sie über Ihre Zeichenkette denken, nicht übereinstimmen. Schreiben Sie einen Einzeiler wie: perl -E 'while(<>){ chomp; say length }'
und geben Sie ein und Sie erhalten 12... nicht die richtige Antwort, sondern 4.
Das liegt daran, dass Perl davon ausgeht, dass Ihre Zeichenkette kein Text ist. Sie müssen ihm sagen, dass es sich um Text handelt, bevor es Ihnen die richtige Antwort gibt.
Das ist ganz einfach; das Encode-Modul verfügt über die entsprechenden Funktionen. Der generische Einstiegspunkt ist Encode::decode
(oder use Encode qw(decode)
natürlich). Diese Funktion nimmt eine Zeichenkette aus der Außenwelt (wir nennen sie "Oktette", eine schicke Umschreibung für "8-Bit-Bytes") und wandelt sie in einen Text um, den Perl versteht. Das erste Argument ist ein Zeichencodierungsname, wie "UTF-8" oder "ASCII" oder "EUC-JP". Das zweite Argument ist die Zeichenkette. Der Rückgabewert ist ein Perl-Skalar, der den Text enthält.
(Außerdem gibt es Encode::decode_utf8
die UTF-8 als Kodierung voraussetzt).
Wenn wir unseren Einzeiler umformulieren:
perl -MEncode=decode -E 'while(<>){ chomp; say length decode("UTF-8", $_) }'
Wir tippen ein und erhalten "4" als Ergebnis. Erfolg.
Das ist die Lösung für 99% aller Unicode-Probleme in Perl.
Der Schlüssel ist, dass Sie jeden Text, der in Ihr Programm kommt, dekodieren müssen. Das Internet kann keine Zeichen übertragen. Dateien können keine Zeichen speichern. Es gibt keine Zeichen in Ihrer Datenbank. Es gibt nur Oktette, und Oktette können in Perl nicht als Zeichen behandelt werden. Sie müssen die kodierten Oktette mit dem Modul Encode in Perl-Zeichen dekodieren.
Die andere Hälfte des Problems besteht darin, Daten aus Ihrem Programm herauszuholen. Das ist einfach; Sie sagen einfach use Encode qw(encode)
entscheiden Sie, in welcher Kodierung Ihre Daten vorliegen sollen (UTF-8 für Terminals, die UTF-8 verstehen, UTF-16 für Dateien unter Windows usw.), und geben Sie dann das Ergebnis von encode($encoding, $data)
anstatt nur auszugeben $data
.
Diese Operation wandelt die Zeichen von Perl, mit denen Ihr Programm arbeitet, in Oktette um, die von der Außenwelt verwendet werden können. Es wäre viel einfacher, wenn wir einfach Zeichen über das Internet oder an unsere Terminals senden könnten, aber das geht nicht: nur Oktette. Wir müssen also Zeichen in Oktette umwandeln, sonst sind die Ergebnisse undefiniert.
Zusammengefasst: alle Ausgänge kodieren und alle Eingänge dekodieren.
Wir werden nun über drei Probleme sprechen, die dies zu einer kleinen Herausforderung machen. Das erste sind die Bibliotheken. Verarbeiten sie Text richtig? Die Antwort ist: Sie versuchen es. Wenn Sie eine Webseite herunterladen, gibt Ihnen LWP das Ergebnis als Text zurück. Wenn Sie die richtige Methode für das Ergebnis aufrufen (und das ist zufällig decoded_content
, nicht content
der nur der Oktettstrom ist, den er vom Server erhalten hat). Datenbanktreiber können unzuverlässig sein; wenn Sie DBD::SQLite nur mit Perl verwenden, wird es funktionieren, aber wenn ein anderes Tool Text in einer anderen Kodierung als UTF-8 in Ihrer Datenbank gespeichert hat... nun... es wird nicht korrekt gehandhabt werden, bis Sie Code schreiben, um es korrekt zu behandeln.
Die Ausgabe von Daten ist in der Regel einfacher, aber wenn Sie "wide character in print" sehen, dann wissen Sie, dass Sie irgendwo die Kodierung durcheinander bringen. Diese Warnung bedeutet "Hey, du versuchst, Perl-Zeichen nach außen zu geben, und das macht keinen Sinn". Ihr Programm scheint zu funktionieren (weil die Gegenseite die rohen Perl-Zeichen normalerweise korrekt verarbeitet), aber es ist sehr fehlerhaft und könnte jeden Moment aufhören zu funktionieren. Reparieren Sie es mit einer expliziten Encode::encode
!
Das zweite Problem ist UTF-8 kodierter Quellcode. Es sei denn, Sie sagen use utf8
am Anfang jeder Datei, wird Perl nicht davon ausgehen, dass Ihr Quellcode UTF-8 ist. Das bedeutet, dass jedes Mal, wenn Sie etwas sagen wie my $var = ''
Wenn du das tust, fügst du Müll in dein Programm ein, der alles auf schreckliche Weise kaputt macht. Sie müssen nicht "utf8" verwenden, aber wenn Sie es nicht tun, werden Sie muss Verwenden Sie in Ihrem Programm keine Nicht-ASCII-Zeichen.
Das dritte Problem ist die Art und Weise, wie Perl mit The Past umgeht. Vor langer Zeit gab es so etwas wie Unicode noch nicht, und Perl nahm an, dass alles Latin-1-Text oder binär war. Wenn also Daten in Ihr Programm kommen und Sie beginnen, sie als Text zu behandeln, behandelt Perl jedes Oktett als ein Latin-1-Zeichen. Als wir nach der Länge von "" fragten, erhielten wir deshalb 12. Perl nahm an, dass wir mit der Latin-1-Zeichenkette "æååã" arbeiteten (die aus 12 Zeichen besteht, von denen einige nicht druckbar sind).
Dies wird als "implizites Upgrade" bezeichnet und ist eine durchaus sinnvolle Sache, aber nicht das, was Sie wollen, wenn Ihr Text nicht Latin-1 ist. Deshalb ist es wichtig, die Eingabe explizit zu dekodieren: Wenn Sie es nicht tun, wird Perl es tun, und es könnte es falsch machen.
Es gibt Probleme, wenn die Hälfte der Daten eine richtige Zeichenkette ist und ein anderer Teil noch binär ist. Perl wird den Teil, der noch binär ist, so interpretieren, als ob es sich um Latin-1-Text handelt, und ihn dann mit den richtigen Zeichendaten kombinieren. Das lässt es so aussehen, als ob die korrekte Behandlung der Zeichen Ihr Programm kaputt gemacht hätte, aber in Wirklichkeit haben Sie es einfach nicht genug repariert.
Hier ein Beispiel: Sie haben ein Programm, das eine UTF-8-kodierte Textdatei liest, und Sie fügen eine Unicode PILE OF POO
in jede Zeile und drucken Sie sie aus. Sie schreiben es so:
while(<>){
chomp;
say "$_ ";
}
Und dann auf einige UTF-8 kodierte Daten, wie:
perl poo.pl input-data.txt
Es druckt die UTF-8-Daten mit einem poo am Ende jeder Zeile. Perfekt, mein Programm funktioniert!
Aber nein, Sie machen nur eine binäre Verkettung. Du liest Oktette aus der Datei, entfernst ein \n
mit chomp, und dann das Anhängen der Bytes in der UTF-8-Darstellung der PILE OF POO
Charakter. Wenn Sie Ihr Programm überarbeiten, um die Daten aus der Datei zu dekodieren und die Ausgabe zu kodieren, werden Sie feststellen, dass Sie Müll ("ð©") anstelle von poo erhalten. Das wird Sie zu der Annahme verleiten, dass das Dekodieren der Eingabedatei der falsche Weg ist. Ist es aber nicht.
Das Problem besteht darin, dass der Poo implizit als Latin-1 aufgewertet wird. Wenn Sie use utf8
um den wörtlichen Text anstelle des binären zu verwenden, dann wird es wieder funktionieren!
(Das ist das größte Problem, das ich sehe, wenn ich Leuten mit Unicode helfe. Sie haben einen Teil richtig gemacht, und das hat ihr Programm kaputt gemacht. Das ist das Traurige an undefinierten Ergebnissen: Sie können lange Zeit ein funktionierendes Programm haben, aber wenn Sie anfangen, es zu reparieren, geht es kaputt. Machen Sie sich keine Sorgen; wenn Sie Ihrem Programm Kodier-/Dekodieranweisungen hinzufügen und es bricht, bedeutet das nur, dass Sie noch mehr Arbeit vor sich haben. Das nächste Mal, wenn Sie von Anfang an mit Unicode planen, wird es viel einfacher sein).
Das ist eigentlich alles, was Sie über Perl und Unicode wissen müssen. Wenn Sie Perl sagen, was Ihre Daten sind, hat es die beste Unicode-Unterstützung unter allen gängigen Programmiersprachen. Wenn Sie jedoch davon ausgehen, dass Perl auf magische Weise weiß, welche Art von Text Sie ihm geben, dann werden Sie Ihre Daten unwiderruflich zerstören. Nur weil Ihr Programm heute auf Ihrem UTF-8-Terminal funktioniert, heißt das noch lange nicht, dass es morgen mit einer UTF-16-kodierten Datei funktionieren wird. Gehen Sie also jetzt auf Nummer sicher und ersparen Sie sich das Kopfzerbrechen, die Daten Ihrer Benutzer zu zerstören!
Der einfache Teil der Handhabung von Unicode ist die Kodierung der Ausgabe und die Dekodierung der Eingabe. Der schwierige Teil besteht darin, alle Eingaben und Ausgaben zu finden und zu bestimmen, welche Kodierung sie haben. Aber dafür bekommt man ja auch das große Geld :)
4 Stimmen
Hallo Leute - es gibt ein paar Anzeichen, die auf diese Kommentare hinweisen. Ich habe einen Schnappschuss der Kommentare hier gemacht und sie in diesen Chatroom gestellt, wo ihr die Diskussion weiterführen könnt: chat.stackoverflow.com/rooms/846/
16 Stimmen
Es tut mir leid, aber ich stimme @tchrist zu - UTF-8 ist extrem schwierig. Es gibt kein Framework oder Tool, das einfach "einen Schalter umlegt" und es dann richtig handhabt. Das ist etwas, worüber man direkt nachdenken muss, wenn man seine Anwendung entwirft - nichts, was irgendein Framework oder eine Sprache für einen erledigen kann. Wenn rakudo nur zufällig für Sie funktioniert hat, waren Sie nicht abenteuerlich genug mit Ihren Testfällen -- denn es wird mehrere der Beispiele in @tchrist's Antwort nehmen und dann ausschlachten.
12 Stimmen
Was genau erhoffen Sie sich von Moose oder Modern::Perl? Auf magische Weise zufällig kodierte Zeichendaten in Dateien und Datenbanken wieder in gültige Daten verwandeln?
2 Stimmen
@Billy ONeal: Wenn ich die @tchrist-Liste überfliege, gibt es nicht das eine und einzige Heilmittel. Ich stimme zu. Dennoch gibt es eine gemeinsame Ebene der UTF-8-Behandlung, die gerade so pluggbar ist und die Entwicklern hilft, ins Spiel zu kommen. Ich denke, das Wissen in diesem neuen Modul
utf8::all
ist ein sehr guter Anfang. Wenn es (oder eine ähnliche Funktionalität) in Kern undperluniintro
es als Schnellstart vorschlagen, wäre viel besser.0 Stimmen
@jrockway: Was ist der Zweck von Modern::Perl? Reduktion von Boilerplate und Einführung von Best Practices der heute in Perl verfügbaren Technologien. Einschließlich UTF-8 Handhabung passt hier sehr gut, IMHO. Ähnlich bei Moose: es ist ein modernes Objektsystem für Perl. Warum also nicht einen weiteren Schritt machen und UTF-8 als Standardzeichensatz in Moose einbauen?
15 Stimmen
Was soll das bedeuten? Moose hat nichts mit Textmanipulation zu tun. Warum sollte es etwas über die Zeichenkodierung wissen, geschweige denn eine Standardkodierung für Sie auswählen? (Wie auch immer, der Grund, warum die Pragmas, die Sie auflisten, die Kodierung nicht berühren, ist, dass die Konvention für Perl-Pragmas ist, sich auf lexikalisch Verhalten. Die Annahme, dass die gesamte Welt, einschließlich anderer Module, UTF-8 ist, ist einfach falsch. Dies ist nicht PHP oder Ruby hier.)
9 Stimmen
(Auch ... "die meisten Modern Perl Anwendungen" brechen bei UTF-8? Ich habe sicherlich noch nie eine Anwendung geschrieben, weder Perl noch andere, die nicht Unicode-sauber ist).
15 Stimmen
Nb. tchrist (Tom Christiansen) hat seine [ ausbildung.perl.com/OSCON2011/index.html Tom Christiansens Materialien für die OSCON 2011] über Unicode. Das Material mit dem Titel "Unicode Support Shootout: The Good, The Bad, & the (mostly) Ugly" behandelt die Unicode-Unterstützung in verschiedenen Programmiersprachen. Nur Google Go und Perl5 bieten volle Unicode-Unterstützung, nur Google Go ist integriert (Perl6 wird nicht erwähnt).
0 Stimmen
Bezieht sich Ihre Frage speziell auf ein bestimmtes Betriebssystem? Die meistgewählte Antwort scheint Linux-spezifisch zu sein. Oder zumindest spezifisch für andere Unices als MacOS X.
0 Stimmen
@hippietrail: Ich arbeite hauptsächlich mit Linux, aber ich habe viele UTF-8-bezogene Perl-Fragen auch für Win gesehen. Ich habe zu wenig Kenntnisse über MacOS X, aber soweit ich weiß, sollten die gleichen Fragen auch für Mac aktuell sein. Wenn nicht, bin ich froh darüber und freue mich darauf, bald mit Perl auf dem Mac zu arbeiten.
6 Stimmen
Wenn ich mich auf einem POSIX-System befinde und
ENV['LC_ALL']
z.B. auf "en_US.UTF-8" gesetzt ist, dann ist das eine explizite Absichtserklärung, die Perl honorieren sollte, indem es annimmt, dass seine Standardeingabe als UTF-8 kodiert ist, und seine Standardausgabe ebenso kodiert. Wenn mein Code nicht funktioniert, weil er einige der vielen Feinheiten von Unicode nicht beherrscht, sollte ich ihn vielleicht nicht in einer Umgebung laufen lassen, die behauptet sein Unicode. Ich verstehe nicht, warum Perl die Locale-Einstellungen ignorieren sollte zugunsten dessen, was auch immer der Standard ist.0 Stimmen
Ich habe nicht viel darüber nachgedacht, aber utf8::all scheint für meine grundlegenden Bedürfnisse zu funktionieren. FWIW, ich denke, die Art der (öffentlichen) Einfachheit der utf-8 Verwendung in Java ist etwas, das Perl enorm profitieren könnte.
1 Stimmen
Ich weiß, das ist ein wenig off-topic und trolly, aber warum nicht loswerden anachronistischen Sprachen wie Perl und PHP und nur Python verwenden und haben Unicode der Standard sein. Um in eine bestimmte Kodierung zu konvertieren, tun Sie
'string'.encode('utf-8')
(Sie erhaltenb'string'
) und um diese binäre Zeichenkette wieder in Unicode zu konvertieren, tun Sieb'string'.decode('utf-8')
(Sie erhalten'string'
). Jetzt können Sie aufhören, darüber nachzudenken. Das wäre meine Art, die Dinge im Jahr 2019 zu erledigen. Alt zu sein bedeutet in der Regel, stabil zu sein, aber es bedeutet oft auch, dass man hässliche Dinge nicht loswird (das betrifft natürlich auch Python).1 Stimmen
@Nils Denn wenn man sich um die Kodierung und Dekodierung binärer Bitmuster kümmern muss, macht man es falsch. UTF-8 ist nichts anderes als eine Kodierung, und Sie sollten sich niemals Gedanken über die einzelnen, bytegroßen Codeeinheiten machen müssen. Sie sollten höchstens über abstrakte Codepunkte nachdenken - und nicht darüber, ob sie groß- oder klein-endlich sind :) Kodierung und Dekodierung sollten praktisch immer an den Grenzen der Schnittstellenschichten für den Austausch mit externen Einheiten stattfinden. Vertrauen Sie mir, die Intrakonvertierung von Codepunkten mit Bitmustern ist die am wenigsten Ihrer Sorgen, wenn es um Unicode geht.
2 Stimmen
@tchrist Ich bin mir nicht sicher, ob ich Ihren Standpunkt verstehe. Python verwendet intern überall Unicode und es gibt keinen Grund, sich über Bits und Bytes Gedanken zu machen. len('aou') == len('äöü') == len(''). Wenn ein Modul keine Kodierungsdeklaration hat, nimmt Python utf-8 an und dekodiert es in Unicode. Die Windows-Dateisystem- und Konsolenkodierung wurde in v3.6 auf UTF-8 umgestellt. Alle relevanten python 3 Bibliotheken kodieren in utf-8 und verwenden intern unicode. Nur wenn open() Dateien im Textmodus ohne den Kodierungsparameter öffnet (was keine Bibliothek tut), wird Python immer noch locale.getpreferredencoding() bevorzugen.
3 Stimmen
Das wird sich in Perl 7 ändern .