Anzeige:
Ergebnis 1 bis 11 von 11

Thema: Filecheck schlägt fehl wegen Zeichencodierung

  1. #1
    Registrierter Benutzer Avatar von Molaf
    Registriert seit
    15.11.2004
    Beiträge
    127

    Filecheck schlägt fehl wegen Zeichencodierung

    Hallo allerseits,

    ich habe ein sonderbares Verhalten erleben dürfen im Zusammenspiel von Perl und Windows.

    Während ein Perl-Programm nach einer Dateinamens-Eingabe erstmal prüft, ob die Datei vorhanden ist, schlägt diese Prüfung unter Windows gelegentlich fehl.
    Und zwar immer dann, wenn die Datei in einem Dateipfad mit einem 'µ' liegt.

    Jetzt kann ich zwar Datenströme beim Öffnen codieren, aber bei einem Dateicheck bin ich ratlos.
    Code:
    unless (-f $DateiMitPfad) {print "Datei existiert nicht!\n";}
    else ....
    Weder in der Doku zu Perl noch über Google habe ich jetzt was zu dem Thema gefunden. Weiss zufällig jemand weiter?

    Gruß,
    Molaf

  2. #2
    Registrierter Benutzer
    Registriert seit
    07.05.2007
    Beiträge
    656
    Moin,

    vielleicht hilft Dir das Modul "Encode" weiter - hier die Funktion decode('cp1252', $DateiMitPfad).

    Der Zeichensatz ist jetzt einfach mal gesponnen, das musst Du auf Dein System ggf. anpassen.

    Jan

  3. #3
    Registrierter Benutzer Avatar von Molaf
    Registriert seit
    15.11.2004
    Beiträge
    127
    Ich habe gestern abend noch viel mit Encode::Encoder herum experiemtiert, dummerweise hatte ich keine Ahnung welchen Zeichensatz Windows nutzt.

    Ich hatte immer wieder mit cp850 probiert, hin und her kodiert, hat aber alles nüx gebracht. (cp850 ist der Zeichensatz der Eingabeaufforderung, daher bin ich darauf gekommen).

    Jetzt habe ich zufällig in der Dokumentation zu Tk::chooseDirectory bei CAVEATS die Lösung gefunden, gleich mit Beispielcode:
    Code:
        use Encode;
        ...
        my $dir = $mw->chooseDirectory;
        $dir = encode("windows-1252", $dir);
        opendir DIR, $dir or die $!;
        ...
    Ergänzung zum Problem: Nicht nur µ war problematisch, auch Umlaute in Dateinamen haben zum Nicht-Erkennen geführt, wenn man die Namensstrings entweder fest im Code vorgibt oder per GUI-Funktion, bspw. von Tk holt.
    Tk arbeitet intern wohl komplett mit utf8 und konvertiert deswegen auch alle Strings intern um.

    Danke für die Hilfe.
    Molaf

  4. #4
    Registrierter Benutzer
    Registriert seit
    07.05.2007
    Beiträge
    656
    Moin,

    seit 8.04 arbeitet IMHO Perl::Tk mit utf-8, vorher ging das gar nicht (ich habe mir für eine kleine GUI mal die Finger verrenkt, um mit encode/decode eine vernünftige Ausgabe hinzukriegen, und dann doch mein System auf einen neuen Stand gebracht ;-).

    Ob Perl inzwischen standardmäßig mit utf-8 arbeitet, weiß ich gar nicht, ich setze immer ein "use utf8;". Du kannst das übrigens mit "no utf8;" auch abschalten.

    Die Crux ist, dass Perl intern nicht mit utf8, sondern mit einer internen Kodierung arbeitet. Du musst also auch von außen kommende Daten (Dateiinhalte, Dateinamen) mit decode ins Perl-interne Format umwandeln, ehe Du damit was anstellen kannst (zumindest laut "perldoc Encode" - und nach eigenen Erfahrungen ;-).

    Jan

  5. #5
    Registrierter Benutzer Avatar von Molaf
    Registriert seit
    15.11.2004
    Beiträge
    127
    Aloa,

    use utf8 nutze ich auch gelegentlich, speziell, wenn ich nicht sicher bin ob der Editor die Datei auch als utf8 codiert oder das nur behauptet (passiert unter Windoof ab und an bei Textpad und oft bei Geany). Grade wenn Label und Buttons Umlaute enthalten und diese im Programm sonderbare Sonderzeichen werden, hilft ein use utf8 und alles ist wieder ok

    Bei Webanwendungen verkneife ich mir das aber, da führt das meistens zum Gegenteiligen Effekt. Ich habe eben mal meine Sammlung hier durchgesehen, habe aber leider kein passendes Beispiel gefunden.

    Solange man den Input halbwegs unter Kontrolle hat, sollte ein Setzen oder Lassen von utf8 das Problem lösen, eine feste Regel kann ich aber nicht ableiten.

    Molaf

  6. #6
    Registrierter Benutzer
    Registriert seit
    07.05.2007
    Beiträge
    656
    Moin,

    bei meiner Webseite ist das genau andersrum - ich musste zwangsweise auf utf-8 umstellen ;-) Passt mir aber ganz gut, meine SuSE 11.1 verwendet mittlerweile ohne Bruch überall utf-8 (zumindest habe ich noch keine Ausreißer entdeckt) und damit habe ich auch keinen Stress, wenn ich Templates im vi oder auf der Kommandozeile bearbeite. Mein "CMS" (besteht im Wesentlichen aus einer Sammlung von Perl-, Perl::Tk- und Shell-Scripts) läuft jetzt durchgehend auch mit utf-8.

    Jan

  7. #7
    Registrierter Benutzer Avatar von Molaf
    Registriert seit
    15.11.2004
    Beiträge
    127
    Huch, hier wird weiter geantwortet

    Ich nutze privat ein Debian/Sidux, welches ebenfalls utf8 nutzt (endlich).

    Wenn ich Webseiten-Dateien mit utf8-Kodierung speichere, und bei Encoding auch utf8 angebe, werden die Seiten vom Browser richtig als utf8 erkannt und angezeigt.

    Füge ich zusätzlich im Programmkopf ein use utf8 ein, dann wird die Seite zwar noch als utf8 erkannt, aber die Zeichen sind dann fehlerhaft dargestellt, offenbar wird dann bei der Ausgabe noch wild umkodiert.

    Naja, mit etwas Glück ist im kurzlebigen Computergeschäft in etwa 20 Jahren alles auf utf8 umgestellt und man muss sich keine Sorgen mehr machen.

  8. #8
    Registrierter Benutzer
    Registriert seit
    07.05.2007
    Beiträge
    656
    Moin,

    Zitat Zitat von Molaf Beitrag anzeigen
    ...Füge ich zusätzlich im Programmkopf ein use utf8 ein, dann wird die Seite zwar noch als utf8 erkannt, aber die Zeichen sind dann fehlerhaft dargestellt, offenbar wird dann bei der Ausgabe noch wild umkodiert
    Wie gesagt, Perl arbeitet mit einer internen Kodierung. Ich habe es mir angewöhnt (und bisher klappt das), dass ich beim Lesen aus Dateien, ... grundsätzlich alles mit decode('utf-8', ...) ins interne Format umwandle und beim Schreiben dann wieder ein encode('utf-8', ...) anwende. Ich finde das zwar eher suboptimal, aber einen anderen sauberen Weg habe ich noch nicht entdeckt.

    Jan

  9. #9
    Registrierter Benutzer
    Registriert seit
    01.04.2009
    Ort
    Essen
    Beiträge
    25
    Die Crux ist, dass Perl intern nicht mit utf8, sondern mit einer internen Kodierung arbeitet. ..... Wie gesagt, Perl arbeitet mit einer internen Kodierung.
    Nein, Perl arbeitet intern mit utf-8, oder mit iso-8859-1. Machst du decode() hast du intern immer ein UTF-8 String. Mit encode() wird es wieder nach ISO-8859-1 umgewandelt. Besser ist es man hält letzteres einfach für Binär. (Zummindest gilt dies ab Perl 5.8)

    ---

    Eigentlich sind die Regeln ganz einfach wenn man sie mal verstanden hat. Du musst beachten das alles was von ausen rein kommt also I/O das du dort eigentlich immer ein decode() machen musst. Und alles was heraus geht I/O mäßig musst du vorher ein encode() machen.

    Mit decode() wandelst du alles ins Interne UTF-8 Format um. Mit encode() wird es wieder nach ISO-8859-1 umgewandelt, bzw es wird als Binär angesehen und die eigentliche Kodierung bleibt erhalten.

    Machst du gar kein encode, dann macht Perl bei einem print() automatisch ein encode. Behandelt den internen String allerdiengs als ISO-8859-1 kodiert, sprich UTF-8 Zeichen die im ISO-8859-1 vorkommen werden dargestellt. Bei anderen zeichen wird ein Fragezeichen angezeigt und Perl spuckt auch eine Warnung aus. "Wide-Character in print" oder ähnliches. Was dir dann sagt das ein UTF-8 zeichen nicht nach ISO-8859-1 gewandelt werden konnte. Da es dort das Zeichen nicht gibt.

    Mit "use utf8;" sagst du Perl das der Sourcecode selber in UTF-8 Gespeichert ist.

    Du kannst Perl auch so einstellen das er automatisch alle I/O Sachen automatisch encode() und decode() soll. So das du dir das im Sourcecode ersparen kannst. Dafür musst du dann am anfang.

    "use open 'encoding(UTF-8)'" hinzufügen. So das er alles eingehende automatisch mit decode("UTF-8", ...) macht, und alles was heraus geht automatisch mit encode("UTF-8", ...) behandelt.

    Ausnahme ist STDIN, STDOUT, STDERR, die werden immer noch Standardmäßig als ISO-8859-1 Handles angesehen. Möchtest du diese auch noch anpassen so emfpiehlt sich

    "use open ':std'"

    Damit werden die STD* Handles angepasst und so Codiert wie du es davor angegeben hast. Du kannst auch für IN oder OUT andere Codierungen wählen. Mehr dazu steht hier: http://perldoc.perl.org/open.html

    Wenn du soetwas machst ist es nur wichtig das wenn du Binärdateien öffnest du auch kein "binmode($fh)" vergist.

    Kurz nochmal zusammengefasst. Wenn du überall UTF-8 hast. Sprich Dateien, Terminal, Sourcecode und du überall mit UTF-8 arbeiten möchtest empfiehlt sich folgender Header.

    Code:
    #!/usr/bin/perl
    use strict;
    use warnings;
    use utf8;
    use open 'encoding(UTF-8)';
    use open ':std';
    Wenn du übrigens manuel (en|de)codierst ist es immer gut "utf-8" anstatt "utf8" zu schreiben. Also mit Bindestrich, ohne Bindestrich hat es eine andere bedeutung und es gibt nur eine "laxe" überprüfung. Wo manche ungültigen UTF-8 Kombination erlaubt sind.

    Bei einem Windows System kannst du dann bei encoding() anstatt "UTF-8" "windows-1252" hinschreiben wenn er das (en|de)codieren automatisch vornehmen soll.
    Geändert von Sid Burn (25-05-2009 um 16:56 Uhr)
    Falsch zu liegen ist kein Misserfolg. Es sollte gefeiert werden, da die Erkenntnis etwas Falsch gemacht zu haben Verstehen und Erkenntnis auf ein neues Level anhebt.

  10. #10
    Registrierter Benutzer
    Registriert seit
    07.05.2007
    Beiträge
    656
    Moin,

    dann verstehe ich folgende Aussage nicht (aus "perldoc Encode"):

    $octets = encode(ENCODING, $string [, CHECK])
    Encodes a string from Perl's internal form into ENCODING and returns
    a sequence of octets.
    ...
    $string = decode(ENCODING, $octets [, CHECK])
    Decodes a sequence of octets assumed to be in ENCODING into Perl's
    internal form and returns the resulting string.
    Es mag in meinen Programmen im Zusammenhang mit Perl::Tk stehen (ich habe es im Moment nicht im Kopf, ob ich den Effekt auch schon woanders hatte), aber wenn ich aus Dateien (die UTF-8 enthalten) Daten an GUI-Komponenten (Entry, Text, ...) übergebe, dann muss ich zwingend mit decode/encode arbeiten, ansonsten kommt Zeichensalat heraus.

    Jan

  11. #11
    Registrierter Benutzer
    Registriert seit
    01.04.2009
    Ort
    Essen
    Beiträge
    25
    decode() nimmt einen String entgegen und Codiert ihn in Perls internes Format. Das eigentliche Format ist aber kein besonderes Format, sondern es ist simpel einfach nur UTF-8. Das dort nicht UTF-8 steht hat mehrere Gründe. Den für den eigentlichen Nutzer ist es irrelevant wie Perl ihn genau Codiert, der Benutzer/Programmierer soll eigentlich nur daran denken das es Unicode ist, mehr nicht, und man soll sich auch nicht darauf verlassen das es UTF-8 ist.

    Wenn etwas also im Internen Format ist, kann Perl es verstehen. Wenn du damit allerdiengs I/O betreibst, dann musst du bei einer übergabe sagen in welche Zielcodierung er es wieder encodieren soll.

    Durch dieses Encodieren oder eben encode() würde er es vom Internen Format zum Zielformat umwandeln. Wenn die Ausgabe auch UTF-8 sein soll müsste eigentlich nichts mehr gemacht werden. Aber da man sich ja nicht darauf verlassen soll das es UTF-8 ist, wird jeglicher String der im Internen Format vorliegt vor seiner Ausgabe automatisch encodiert.

    Und das Default Encoding was er hier auswählt ist ISO-8859-1

    Für ein besseres beispiel und um das Verständnis zu erhöhen kann ich ein paar Beispiele zeigen. Wichtig ist es zu erfahren wie Perl eine Variable ansieht, dafür kann man sehr gut Devel::Peek nutzen das die C Struktur einer Variablen ausgibt.

    Code:
    #!/usr/bin/perl
    # Core Module
    use strict;
    use warnings;
    use utf8;
    use Encode qw(encode decode is_utf8);
    use Devel::Peek;
    
    my $str = "hälö";
    Dump($str);
    print "[$str]", length($str), "\n";
    
    $str = encode("UTF-8", $str);
    Dump($str);
    print "[$str]", length($str), "\n";
    Zur Umgebung sei gesagt das mein Editor die Datei als UTF-8 Speichert, sowie Terminal ebenfalls alles UTF-8 ist, also ein komplettes UTF-8 System.

    Nun zur ausgabe:
    Code:
    sidburn@sid:~/perl$ ./utf8.pl 
    SV = PV(0x87e06d0) at 0x87fc878
      REFCNT = 1
      FLAGS = (PADMY,POK,pPOK,UTF8)
      PV = 0x87f7dd0 "h\303\244l\303\266"\0 [UTF8 "h\x{e4}l\x{f6}"]
      CUR = 6
      LEN = 8
    [h�l�] 4
    SV = PVMG(0x8843278) at 0x87fc878
      REFCNT = 1
      FLAGS = (PADMY,SMG,POK,pPOK)
      IV = 0
      NV = 0
      PV = 0x8877c70 "h\303\244l\303\266"\0
      CUR = 6
      LEN = 8
      MAGIC = 0x88144f8
        MG_VIRTUAL = &PL_vtbl_utf8
        MG_TYPE = PERL_MAGIC_utf8(w)
        MG_LEN = -1
    [hälö]6
    Wenn man die Dumps erstmal weg lässt dann siehst man das die erste Ausgabe falsch ist, allerdiengs hat er bei der Länge korrekt "4" angegeben. Sprich es waren 4 Zeichen. Bytes waren es mehr, nämlich 6.

    Bei der zweiten Ausgabe sieht man die Ausgabe korrekt, allerdiengs gibt uns length() 6 zurück. Es betrachtet also nicht die Zeichen sondern wirklich die Bytes.

    Bei den Dumps ist eigentlich erstmal nur FLAGS und PV Interessant. Bei Flags stehen eben mackierungen die eine Variable besitzt, und in PV steht der Inhalt.

    Beim ersten Dump sehen wir hierbei das die Variable das "UTF8" Flag besitzt. Daher es ist im Internen Format von Perl codiert. Beim PV sehen wir letztendlich nochmals die Interne Darstellung, einmal in Oktaler Notation "\0303\0244" und einmal mit "\x{e4}". Letztendlich bedeutet beides das gleiche. Die zweite Notation ist nur Perls Form direkt ein Unicode Zeichen einzufügen, unabhägig von seiner Codierung. Die Oktale Form kann sich ja unterscheiden. z.B. wenn Perl sich entscheided UTF-16 als Internes Format zu nehmen.

    Trotzdem ist die erste Ausgabe Falsch. Warum? Weil Perl eben vor jeder Ausgabe automatisch vom Internen Format ins ISO-8859-1 Format umwandelt. Das Interne Format wird durch die Flags gekennzeichnet. Den bei unserem ersten Dump steht "UTF8" noch als Flag. Was passiert nun bei einer Umwandlung? Wenn man einen String der bereits UTF8 gelfagt ist nach ISO-8859-1 codiert kommt dann danach folgender Dump heraus:
    Code:
    SV = PVMG(0x9019278) at 0x8fd2878
      REFCNT = 1
      FLAGS = (PADMY,SMG,POK,pPOK)
      IV = 0
      NV = 0
      PV = 0x904c660 "h\344l\366"\0
      CUR = 4
      LEN = 8
      MAGIC = 0x8fea4f8
        MG_VIRTUAL = &PL_vtbl_utf8
        MG_TYPE = PERL_MAGIC_utf8(w)
        MG_LEN = -1
    Wie man also sieht ist das Ergebnis nur noch 4 Bytes groß. Die "ä" und "ö" sind zu einem oktalen zeichen geworden. \344 ist Oktal und ist Dezimal = 288, Hex = E4.

    Schaust du auf der ISO-8859-1 Tabelle nach http://en.wikipedia.org/wiki/ISO_8859-1 sieht man das dies das Zeichen "ä" in iso-8859-1 Kodierung ist.

    Um es also nachzuvollziehen. mit decode() wandeln wir es ins Interne Format um. Perl kennt danach praktisch die Zeichenbedeutung. Und weiß das es ein "ä" ist. Das Interne Format wird mit UTF-8 dargestellt, obwohl das eigentlich irrelevant ist. Und bei einem encode() geben wir an in welchen zeichencodierung er unseren String codieren soll. Default ist es ISO-8859-1 also nimmt er die Repräsentation des zeichens "ä" im ISO-8859-1 Format vor was dann zum Resultat \344 wird.

    Wenn man das Terminal übrigens auf ISO-8859-1 schaltet würde man korrekt "hälö" sehen.

    Jetzt aber wieder zurück zum anfang, im Code hatte ich ja mit
    Code:
    $str = encode("UTF-8", $str);
    den String Codiert, und ausgegeben und die Ausgabe war korrekt. Wenn du dir nun den PV anschaust und die Oktale darstellung wirst du feststellen dass sich letztendlich überhaupt nichts geändert hat. Es war schon UTF-8 codiert und ist auch so geblieben. Allerdiengs ist das Flag "UTF8" bei den Flags verschwunden.

    Daher sieht Perl nun das es nicht mehr im Internen Format ist, und bei einer Ausgabe wird nicht mehr automatisiert ein encode() gemacht.

    Module wie Tk oder generell C oder XS basierte Module haben aber noch ein weiteres Problem. Den viele behandeln das UTF8 Flag nicht. Daher jedes Modul das irgendeine Ausgabe macht ohne das Flag zu beachten codiert letztendlich wieder nach ISO-8859-1. Weiterhin gibt es auch Module die einfach die Oktale darstellung nutzen und es generell immer als UTF-8 ansehen oder nicht. DBD::mysql sieht z.B. Strings immer als in UTF-8 Kodiert an wenn man bei der verbindung das Flag "mysql_enable_utf8 => 1" setzt. Bzw DBD::mysql aktiviert auch immer das UTF8 Flag unabhängig davon ob die Daten wirklich UTF-8 Kodiert sind oder nicht. Genauso kann es sein das manche Module doppelt decodieren da sie annehmen sie bekommen kein UTF-8 geflagt String etc.

    Zwar kann man absolut alle solche Probleme mit einem utf8::is_utf8($str) und einer if abfrage überprüfen und nur je nach rückgabe (en|de)codieren aber anscheind ist das wohl den meisten zu komplex. Bzw da viele Module von Amerikanern kommen und die sich meist einen Dreck um Unicode kümmern (brauchen sie ja nicht) wird soetwas meist auch nicht berücksichtigt. Daher du musst dich dann selber darum kümmern keine UTF-8 geflagten Strings zu übergeben und vorher immer alles "encodieren". Tk wird wohl das gleiche Problem haben.

    Da ich "Tk" noch nie genutzt habe kann ich dir nicht sagen ob Tk vernünftig UTF-8 bzw überhaupt irgendwelche Codierungen beachten. Wenn es das tun würde, und der Modulautor es korrekt machen möchte, so müsste er eigentlich eine Option einbauen wo man die Codierung angeben muss.

    Hat er das nirgendswo dann geht er entweder davon aus das er nur ungeflagte UTF-8 Strings bekommt oder Codiert bei einer ausgabe einfach immer oder ähnliches was letztendlich zu Fehlern führen kann.


    Zum Schluß. Wichtig ist die Funktionalität der Schalter zu verstehen.
    Wenn du beispielsweise "use utf8;" aktivierst dann sagst du Perl damit das der Sourcecode in einer UTF-8 Kodierung gespeichert ist. Daher beim Programmstart wandelt Perl alle variablen automatisch ins UTF-8 Format um. Bzw würde das UTF8 Flag aktivieren. Lässt du "use utf8" weg so wird die Ausgabe auch auf einen UTF8 Terminal richtig sein ohne ein encode() zu machen. Allerdiengs sieht er dann nicht mehr ein "ä" sondern zwei aufeinanderfolgende Zeichen aus der ISO-8859-1 Kodierung.

    Das führt dazu das length() zum Beispiel nicht die anzahl der Zeichen sondern die Bytes zurück gibt. bei "hälö" also 6 anstatt 4. Auch können Regexe oder andere String Manipulationen zu Fehlern führen.

    Was du nicht mit "use utf8" z.B. nicht sagst ist welche Codierung er bei open() nutzen soll. Wie die STD* Handles zu behandeln sind etc. Um das verhalten zu überschreiben gibt es aber die "use open ..." Pragma, was ich im Post davor schon erklärt hatte.
    Geändert von Sid Burn (27-05-2009 um 14:03 Uhr)
    Falsch zu liegen ist kein Misserfolg. Es sollte gefeiert werden, da die Erkenntnis etwas Falsch gemacht zu haben Verstehen und Erkenntnis auf ein neues Level anhebt.

Lesezeichen

Berechtigungen

  • Neue Themen erstellen: Nein
  • Themen beantworten: Nein
  • Anhänge hochladen: Nein
  • Beiträge bearbeiten: Nein
  •