Ist dieses Endverhalten in von POSIX angegebenen Gruppierungsbefehlen?

7

Die Verwendung in tailKombination mit anderen Standardwerkzeugen in Gruppierungsbefehlen kann einige leistungsstarke Konstruktionen ergeben. So rufen Sie beispielsweise die erste und letzte Zeile einer Datei ab:

$ seq 10 > file
$ { head -n1; tail -n1; } <file
1
10

Wenn Dateiinhalte von einem Rohr zum Zuführen Gruppenbefehl, tailversagt Ausgabe zu erzeugen, weil ein Rohr ist un- lseek der Lage :

$ seq 10 | { head -n1; tail -n1; }
1

Wenn der Inhalt groß genug ist, tailfunktioniert Folgendes:

$ seq 10000 | { head -n1; tail -n1; }
1
10000

Das liegt daran, dass nach dem ersten lseekFehler bekannt ist, taildass es sich nicht um einen aussagekräftigen Dateideskriptor handelt, und dass der Inhalt der Pipe noch nicht vollständig gelesen wurde und der Inhalt bis zum Ende gelesen wird.

Aus Sicht des Benutzers erwarte ich, dass das Verhalten unabhängig von der Größe des Eingabeinhalts konsistent ist. Ich habe POSIX tailund die lseekDokumentation durchgesehen und keine Beschreibung gefunden.

Wird dieses Verhalten von POSIX angegeben? Wenn nicht, wie kann ich das Ergebnis immer konsistent machen?


Ich habe mit GNU-Schwanz und FreeBSD-Schwanz getestet, beide haben das gleiche Verhalten.

cuonglm
quelle
Es könnte erwähnenswert sein, darauf hinzuweisen, dass das Aufteilen solcher Eingaben mit tailwahrscheinlich nicht besonders nützlich ist und tatsächlich zu mehr Arbeit unter der Haube führen kann. Wie Stéphane erwähnt, erfordert es eine zusätzliche Eingabevalidierung für ein, taildas einfach bis zum Ende der Eingabe durchgesucht werden kann, da es diesen Eingabeversatz mit einem vergleichen muss, mit dem es möglicherweise übereinstimmt lseek(), und das Ergebnis ist nicht anders als head -n1 file; tail -n1 file. Ich finde das Zeug nützlicher, wenn man Eingaben von oben while IFS= read -r v; do { printf %s\\n "$v"; head; } >&"$((1+(x=!x)))"; done <in >out1 2>out2
abschneidet
Eigentlich denke ich, wenn Sie tailzumindest gruppieren , sollte es keine Überlappung drucken, also gibt es das natürlich. Entschuldigen Sie.
Mikeserv

Antworten:

7

Beachten Sie, dass das Problem nicht bei, tailsondern bei headhier liegt, das mehr aus der Pipe liest als die erste Zeile, die ausgegeben werden soll (es bleibt also nichts mehr tailzum Lesen übrig ).

Und ja, es ist POSIX-konform.

head ist erforderlich, um den Cursor innerhalb von stdin direkt nach der letzten ausgegebenen Zeile zu belassen, wenn die Eingabe suchbar ist, aber nicht anders.

http://pubs.opengroup.org/onlinepubs/9699919799/utilities/V3_chap01.html :

Wenn ein Standarddienstprogramm eine durchsuchbare Eingabedatei liest und fehlerfrei beendet wird, bevor es das Dateiende erreicht, muss das Dienstprogramm sicherstellen, dass der Dateiversatz in der Beschreibung der geöffneten Datei direkt nach dem letzten vom Dienstprogramm verarbeiteten Byte richtig positioniert ist. Für Dateien, die nicht durchsucht werden können, ist der Status des Dateiversatzes in der Beschreibung der geöffneten Datei für diese Datei nicht angegeben.

Um headdies für eine nicht durchsuchbare Datei tun zu können, müsste sie jeweils ein Byte lesen, was fürchterlich ineffizient wäre¹. Das ist , was die readoder lineDienstprogramm tun oder GNU sedmit der -uOption.

So können Sie ersetzen head -n 20mit , gsed -u 20qwenn Sie dieses Verhalten möchten.

Obwohl Sie hier lieber möchten:

sed -e 1b -e '$b' -e d

stattdessen. Hier nur ein Werkzeugaufruf, also kein Problem mit einem internen Puffer, der nicht zwischen zwei Werkzeugaufrufen geteilt werden kann. Beachten Sie jedoch, dass es bei großen Dateien weniger effizient ist, wenn seddie gesamte Datei gelesen wird, während bei durchsuchbaren Dateien taildas meiste davon übersprungen wird, indem gegen Ende der Datei gesucht wird .

Weitere Informationen zum Puffern finden Sie unter Warum wird die Verwendung einer Shell-Schleife zum Verarbeiten von Text als schlechte Praxis angesehen? .

Beachten Sie, dass taildas Ende des Streams auf stdin ausgegeben werden muss. Während zur Optimierung und für durchsuchbare Dateien Implementierungen möglicherweise bis zum Ende der Datei suchen, um die nachfolgenden Daten von dort abzurufen, ist es nicht zulässig, zu einem Punkt zurückzukehren, der vor dem Aufrufen der ursprünglichen Position zum Zeitpunkt des tailAufrufs liegen würde ( Busybox hatte tailfrüher diesen Fehler.

So zum Beispiel in:

{ cat; tail -n 1; } < file

Auch wenn tailman auf die letzte Zeile zurückblicken könnte file, tut es das nicht. Sein Standard ist ein leerer Stream, der catden Cursor am Ende der Datei verlassen hat. Es ist nicht gestattet, Daten aus diesem Stream zurückzugewinnen, indem in der Datei weiter rückwärts gesucht wird.

(Der obige Text wurde bis zur Klärung durch die Offene Gruppe durchgestrichen und berücksichtigt, dass er von mehreren Implementierungen nicht korrekt ausgeführt wird.)


¹ Die headbuiltin von ksh93(aktiviert , wenn Sie setzen /opt/ast/binvoraus $PATH), für Buchsen (eine Art von nicht-seekable Dateien) anstelle lugt am Eingang (mit recvfrom(..., MSG_PEEK)) vor dem eigentlichen Lesen , um zu sehen , wie viel es machen , lesen muss , dass doesn nicht zu viel lesen. Und greift darauf zurück, jeweils ein Byte für andere Dateitypen zu lesen. Das ist etwas effizienter und ich glaube, es ist der Hauptgrund, warum es seine Pipes mit socketpair()s anstelle von implementiert pipe(). Beachten Sie, dass dies nicht vollständig narrensicher ist, da eine Race-Bedingung ausgelöst werden kann, wenn ein anderer Prozess zwischen dem Peek und dem Read aus dem Socket gelesen wird .

Stéphane Chazelas
quelle
Ich möchte etwas über das tailVerhalten wissen . Nachdem der erste fehlgeschlagen war lseek, begann er, den Inhalt zu lesen. Während des Aufenthalts seq 10000 | tail -n1versuchte es nicht einmal, irgendwelche auszuführen lseek. Hat POSIX das tailVerhalten definiert , wenn es lseekfehlschlägt?
Cuonglm
@cuonglm, wenn meine Lektüre von Stephane Antwort richtig ist, mit seq 10 | { head -n1; tail -n1; }, tailist mit nichts mehr übrig , weil zu lesen headhat gierig den Eingang schlürfte auf. In jedem Fall wäre es nicht sinnvoll tail, eine lseeknicht durchsuchbare Datei zu versuchen
iruvar
@cuonglm, es sucht nur nach durchsuchbaren Dateien (die es wahrscheinlich mit a bestimmt fstat(0)). Siehe auch meine Bearbeitung.
Stéphane Chazelas
@ StéphaneChazelas: Danke für die gründliche Antwort (wie immer :)). Das letzte Wunder in meinem Kopf war, dass lseekPOSIX es zulässt , wenn der erste Versuch fehlschlug, den Versuch tailzu verwerfen lseekund sein Verhalten zu ändern, um die Datei bis zum Ende zu lesen?
Cuonglm
Dies lseek()ist eine Implementierungsoption für die Optimierung. POSIX gibt das Verhalten an, bei dem das Ende der Eingabe und nicht die Details der Vorgehensweise ermittelt werden sollen. Auf jeden Fall sehe ich nicht, dass GNU tailversucht, geschweige denn fehlzuschlagen, lseekwenn die Eingabe eine Pipe ist.
Stéphane Chazelas