2016-04-26 18 views
3

Hier ist eine einfache Textdatei ohne Sonderzeichen, genannt utf-8.txt mit folgendem Inhalt.Warum ändert das Öffnen einer Datei im UTF-8-Modus das Suchverhalten?

foo bar baz 
one two three 

Die neue Linie wird nach der UNIX-Konvention (ein Byte), so dass die gesamte Größe der Datei 26 = 11 + 1 + 13 + 1. (11 = foo bar baz, 13 = one two three.

Wenn ich die Datei mit folgendem perl-Skript lesen

use warnings; 
use strict; 

open (my $f, '<', 'utf8.txt'); 
<$f>; 
seek($f, -4, 1); 
my $xyz = <$f>; 
print "$xyz<"; 

druckt

baz 
< 

Dies wird erwartet, da der Befehl seek vier Zeichen zurückgeht, die neue Zeile und die drei zu baz gehörenden Zeichen.

Wenn ich jetzt die open Anweisung

ändern
open (my $f, '<:encoding(UTF-8)', 'utf8.txt'); 

den Ausgang auf

baz 
< 

, dass der seek Befehl fünf Zeichen ist, geht zurück (oder es geht vier Zeichen zurück, überspringt aber die Neue Zeile).

Wird dieses Verhalten erwartet? Gibt es eine Flagge oder etwas, um dieses Verhalten auszuschalten?

bearbeiten

Per Andrzej A. Filip Vorschlag, wenn ich print join("+",PerlIO::get_layers($f)),"\n"; kurz nach der open Anweisung hinzufügen, druckt er in dem "normalen" offenen Fall: unix+crlf und im open...encoding Fall: unix+crlf+encoding(utf-8-strict)+utf8.

+0

Fügen Sie den folgenden Test nur nach 'open' in beiden Skripten hinzu:' print join ("+", PerlIO :: get_layers ($ f)), "\ n"; ' – AnFi

+0

Bitte beachten Sie meine geänderte Frage –

+0

Es ist verwandt mit die: crlf-Ebene. Umgehung: 'open (my $ f, '<', 'utf8.txt'); Binmodus ($ f); binmode ($ f, ': encoding (UTF-8)'); ' – ikegami

Antwort

3

Für die Suche nach einem TL; DR, seek und tell Arbeit in Bytes. seek sollte immer in Ordnung sein, wenn sie von tell



Die Dokumentation für Perl seek Operator zurückgegebenen Werte verwendet, ist eher ungeschickt, aber es hat diese

FILEHANDLE, POSITION suchen, WHENCE

Die Werte für WHENCE sind 0, um die neue Position in Bytes auf POSITION zu setzen ...

und

Notiere die in Bytes: auch wenn die Dateikennung auf Zeichen für den Betrieb gesetzt wurde (zum Beispiel durch die :encoding(utf8) offene Schicht verwendet wird), sagen() wird Byte-Offsets zurückgeben, keine Zeichenoffsets (weil die Implementierung das seek() und tell() eher langsam macht).

Während dies für das Problem anspielt ist es nicht explizit angegeben

seek und tell Verwendung und das Rück Byteversätze innerhalb der Datei, unabhängig von anderer PerlIO Schicht. Das heißt, sie zu ähnlichen Bedingungen zu sysread arbeiten, die von Perl-Streaming-IO unabhängig ist, obwohl seek und tell Respekt Perl Pufferung während sysread nicht

Es ist nicht nur :utf8 oder :encoding Schichten, die verwirren, welche Einheiten man erwarten kann: die Die Windows-Ebene :crlf hat auch einen Effekt, da CR-LF-Paare vor dem Streaming der Eingabe und nach der Ausgabe in LF konvertiert werden. Das verursacht eindeutig eine Diskrepanz für jede Textzeile, aber soweit ich das beurteilen kann, wird dies in Perls Dokumentation nicht erwähnt; Linux und OSX sind die aufdringlichen hässlichen Schwestern von so ziemlich jeder anderen Perl-Plattform

Schauen wir uns Ihren Code an. Ich habe diesen Code ausführen (es auf den Code in Ihrer Frage identisch ist, das verspreche ich) auf meinem Windows 10 und Windows 7-Systeme und gebootet sogar eine VM mit Windows 98 die gleichen

use warnings; 
use strict; 

open (my $f, '<', 'utf8.txt'); 
print join("+",PerlIO::get_layers($f)),"\n"; 
<$f>; 
seek($f, -4, 1); 
my $xyz = <$f>; 
print "$xyz<"; 

Alle von versuchen, Sie geben diese

was ich erwartet habe, und nicht das, was Sie sagen, dass Sie bekommen. Dies ist von zentraler Bedeutung, da wir reden über Single-Byte-Offsets

Ihre Datei diesen

foo bar baz\r\none two three 

enthält die erste Lese uns zu 13 Zeichen vom Anfang nimmt. Perl hat foo bar baz\r\n gelesen und das CR entfernt, indem es foo bar baz\n an das Programm übergeben hat, das es verwirft. Fein

Jetzt Sie seek($f, -4, 1)

Der dritte Parameter 1 SEEK_CUR, was bedeutet, dass Sie die aktuellen Lesezeiger relativ zur aktuellen Position verschieben mögen.

Bitte

Bitte verwenden Sie keine magischen Zahlen. Perl enthüllt Ihnen hier den zugrunde liegenden C file library und Sie müssen dafür verantwortlich sein. 1 als dritten Parameter zu übergeben, ist geheimnisvoll und unverantwortlich.Niemand, der den Code liest, wird wissen, was Sie

Tun Sie dies

use Fcntl ':seek' 

geschrieben haben, und dann können Sie verständlichen Code wie diesen schreiben. Wenigstens können die Menschen SEEK_CUR google während der gleichen Versuch mit 1 schlimmer wäre als fruchtlos

seek($f, -4, SEEK_CUR) 

wie es dem Rest von uns eine Chance gibt, den Code

zu verstehen, Sie sind also zu 13 Byte zu suchen, addieren -4, das ist 9. Das ist gerade nach dem b von baz, und so bekomme ich az

Das ist, was alle meine Ausführungen Ihres Codes auf all diesen verschiedenen Windows-Maschinen produziert. Ich muss denken, dass das Problem mit Ihrem Code-Steuerelement ist und nicht mit Perl, außer für das Problem mit CRLF

Ich hoffe, dass dies einige Anomalien für Sie erklärt, aber bitte überprüfen Sie Ihren Code und Ihre Ergebnisse.