Kopf frisst zusätzliche Zeichen

15

Der folgende Shell-Befehl sollte nur ungerade Zeilen des Eingabestreams ausgeben:

echo -e "aaa\nbbb\nccc\nddd\n" | (while true; do head -n 1; head -n 1 >/dev/null; done)

Aber anstatt es druckt nur die erste Zeile: aaa.

Dasselbe passiert nicht, wenn es mit der Option -c( --bytes) verwendet wird:

echo 12345678901234567890 | (while true; do head -c 5; head -c 5 >/dev/null; done)

Dieser Befehl wird 1234512345wie erwartet ausgegeben. Dies funktioniert jedoch nur in der coreutils- Implementierung des headDienstprogramms. Bei der Belegtbox- Implementierung werden immer noch zusätzliche Zeichen verwendet, sodass die Ausgabe gerecht ist 12345.

Ich denke, diese spezielle Art der Implementierung dient Optimierungszwecken. Sie können nicht wissen, wo die Zeile endet, sodass Sie nicht wissen, wie viele Zeichen Sie lesen müssen. Die einzige Möglichkeit, keine zusätzlichen Zeichen aus dem Eingabestream zu verbrauchen, besteht darin, den Stream Byte für Byte zu lesen. Das Lesen eines Bytes aus dem Stream kann jedoch langsam sein. Ich vermute also, dass headder Eingabestream in einen ausreichend großen Puffer eingelesen wird und dann die Zeilen in diesem Puffer gezählt werden.

Dies gilt nicht für den Fall, dass die --bytesOption verwendet wird. In diesem Fall wissen Sie, wie viele Bytes Sie lesen müssen. Sie können also genau diese Anzahl von Bytes und nicht mehr lesen. Die corelibs- Implementierung nutzt diese Gelegenheit, die busybox jedoch nicht. Sie liest immer noch mehr Byte als erforderlich in einen Puffer. Dies wird wahrscheinlich zur Vereinfachung der Implementierung durchgeführt.

Also die Frage. Ist es richtig, dass das headDienstprogramm mehr Zeichen aus dem Eingabestream verbraucht, als angefordert wurden? Gibt es einen Standard für Unix-Dienstprogramme? Und wenn ja, gibt es dieses Verhalten an?

PS

Sie müssen drücken Ctrl+C, um die obigen Befehle zu stoppen. Die Unix-Dienstprogramme schlagen beim Lesen darüber hinaus nicht fehl EOF. Wenn Sie nicht drücken möchten, können Sie einen komplexeren Befehl verwenden:

echo 12345678901234567890 | (while true; do head -c 5; head -c 5 | [ `wc -c` -eq 0 ] && break >/dev/null; done)

was ich der Einfachheit halber nicht benutzt habe.

anton_rh
quelle
2
Neardupe unix.stackexchange.com/questions/48777/… und unix.stackexchange.com/questions/84011/… . Auch, wenn dieser Titel auf Movies.SX gewesen wäre, wäre meine Antwort Zardoz :)
Dave_thompson_085

Antworten:

30

Ist es richtig, dass das Dienstprogramm head mehr Zeichen aus dem Eingabestream verbraucht, als angefordert wurden?

Ja, das ist erlaubt (siehe unten).

Gibt es einen Standard für Unix-Dienstprogramme?

Ja, POSIX Volume 3, Shell & Utilities .

Und wenn ja, gibt es dieses Verhalten an?

In seiner Einführung macht es:

Wenn ein Standarddienstprogramm eine suchbare Eingabedatei liest und ohne Fehler beendet, bevor das Dateiende erreicht ist, muss das Dienstprogramm sicherstellen, dass der Dateiversatz in der Beschreibung der geöffneten Datei genau hinter dem letzten vom Dienstprogramm verarbeiteten Byte positioniert ist. Bei nicht durchsuchbaren Dateien ist der Status des Dateioffsets in der Beschreibung der geöffneten Datei für diese Datei nicht angegeben.

headist eines der Standard-Dienstprogramme , daher muss eine POSIX-konforme Implementierung das oben beschriebene Verhalten implementieren.

GNU head ist versuchen , den Dateideskriptor in die richtige Position zu verlassen, aber es ist unmöglich , an Rohren zu suchen, so in Ihrem Test fehlschlägt er die Position wiederherzustellen. Sie können dies sehen mit strace:

$ echo -e "aaa\nbbb\nccc\nddd\n" | strace head -n 1
...
read(0, "aaa\nbbb\nccc\nddd\n\n", 8192) = 17
lseek(0, -13, SEEK_CUR)                 = -1 ESPIPE (Illegal seek)
...

Der readgibt 17 Bytes zurück (alle verfügbaren Eingaben), headverarbeitet vier davon und versucht dann, 13 Bytes zurückzusetzen, kann dies aber nicht. (Sie können hier auch sehen, dass GNU headeinen 8-KiB-Puffer verwendet.)

Wenn Sie festlegen head, dass Bytes gezählt werden sollen (was nicht dem Standard entspricht), weiß das System, wie viele Bytes gelesen werden sollen, sodass es (wenn es auf diese Weise implementiert wird) den Lesevorgang entsprechend einschränken kann. Dies ist der Grund, warum Ihr head -c 5Test funktioniert: GNU headliest nur fünf Bytes und muss daher nicht versuchen, die Position des Dateideskriptors wiederherzustellen.

Wenn Sie das Dokument in eine Datei schreiben und diese stattdessen verwenden, erhalten Sie das gewünschte Verhalten:

$ echo -e "aaa\nbbb\nccc\nddd\n" > file
$ < file (while true; do head -n 1; head -n 1 >/dev/null; done)
aaa
ccc
Stephen Kitt
quelle
2
Man kann stattdessen die line(inzwischen von POSIX / XPG entfernten, aber auf vielen Systemen noch verfügbaren) oder read( IFS= read -r line) Dienstprogramme verwenden, die jeweils ein Byte lesen, um das Problem zu vermeiden.
Stéphane Chazelas
3
Beachten Sie, dass es head -c 5von der Implementierung abhängt , ob 5 Byte oder ein vollständiger Puffer gelesen wird (beachten Sie auch, dass dies head -cnicht der Standard ist). Darauf können Sie sich nicht verlassen. Sie benötigen dd bs=1 count=5eine Garantie, dass nicht mehr als 5 Bytes gelesen werden.
Stéphane Chazelas
Danke @ Stéphane, ich habe die -c 5Beschreibung aktualisiert .
Stephen Kitt
Beachten Sie, dass die integrierte headFunktion jeweils ksh93ein Byte liest, head -n 1wenn die Eingabe nicht suchbar ist.
Stéphane Chazelas
1
@anton_rh, ddfunktioniert nur mit Pipes korrekt, bs=1wenn Sie einen countas read für Pipes verwenden. Möglicherweise wird weniger als angefordert zurückgegeben (aber mindestens ein Byte, es sei denn, eof wird erreicht). GNU ddhat, iflag=fullblockdass dies jedoch gelindert werden kann.
Stéphane Chazelas
6

von POSIX

Das Dienstprogramm head kopiert seine Eingabedateien auf die Standardausgabe und beendet die Ausgabe für jede Datei an einem bestimmten Punkt.

Es sagt nichts darüber aus, wie viel head von der Eingabe gelesen werden muss. Es wäre dumm, es Byte für Byte zu lesen, da es in den meisten Fällen extrem langsam wäre.

Dies wird jedoch im readDienstprogramm builtin / angesprochen : Alle Shells, die ich in readPipes finden kann, sind byteweise und der Standardtext kann so interpretiert werden, dass dies getan werden muss, um nur diese eine einzelne Zeile lesen zu können:

Das Lese - Dienstprogramm wird eine einzelne logische Zeile von der Standardeingabe in einer oder mehr Shell - Variablen gelesen.

Im Fall von read, der in Shell-Skripten verwendet wird, wäre ein häufiger Anwendungsfall etwa so:

read someline
if something ; then 
    someprogram ...
fi

Hier ist die Standardeingabe von someprogramdie gleiche wie die der Shell, aber es ist zu erwarten, dass someprogramalles gelesen wird, was nach der ersten von der readbelegten Eingabezeile kommt, und nicht alles, was nach einem gepufferten Lesen von übrig geblieben ist read. Andererseits ist die Verwendung von headwie in Ihrem Beispiel viel seltener.


Wenn Sie wirklich jede zweite Zeile löschen möchten, ist es besser (und schneller), ein Tool zu verwenden, das die gesamte Eingabe auf einmal verarbeiten kann, z

$ seq 1 10 | sed -ne '1~2p'   # GNU sed
$ seq 1 10 | sed -e 'n;d'     # works in GNU sed and the BSD sed on macOS

$ seq 1 10 | awk 'NR % 2' 
$ seq 1 10 | perl -ne 'print if $. % 2'
ilkkachu
quelle
Lesen Sie jedoch den Abschnitt „INPUT FILES“ in der POSIX-Einführung zu Band 3 ...
Stephen Kitt,
1
POSIX sagt: "Wenn ein Standarddienstprogramm eine suchbare Eingabedatei liest und ohne Fehler beendet, bevor das Dateiende erreicht ist, muss das Dienstprogramm sicherstellen, dass der Dateiversatz in der Beschreibung der geöffneten Datei genau hinter dem letzten von verarbeiteten Byte positioniert ist . das Dienstprogramm für Dateien , die nicht durchsuchbar sind, für diese Datei den Zustand der Datei in der geöffneten Datei Beschreibung Offset ist nicht spezifiziert. "
AlexP
2
Beachten Sie, dass Sie, sofern Sie dies nicht verwenden -r, readmöglicherweise mehr als eine Zeile lesen (ohne IFS=dass auch führende und nachfolgende Leerzeichen und Tabulatoren entfernt würden (mit dem Standardwert von $IFS)).
Stéphane Chazelas
@ AlexP, ja, Stephen hat diesen Teil gerade verlinkt.
ilkkachu
Beachten Sie, dass die integrierte headFunktion jeweils ksh93ein Byte liest, head -n 1wenn die Eingabe nicht suchbar ist.
Stéphane Chazelas
1
awk '{if (NR%2) == 1) print;}'
ijbalazs
quelle
Hellóka :-) und herzlich willkommen auf der Seite! Beachten Sie, wir bevorzugen die ausgearbeiteten Antworten. Sie sollten für die Googler der Zukunft nützlich sein.
Peterh - Wiedereinsetzung von Monica