Beachten Sie, dass das Problem nicht bei, tail
sondern bei head
hier liegt, das mehr aus der Pipe liest als die erste Zeile, die ausgegeben werden soll (es bleibt also nichts mehr tail
zum 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 head
dies 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 read
oder line
Dienstprogramm tun oder GNU sed
mit der -u
Option.
So können Sie ersetzen head -n 20
mit , gsed -u 20q
wenn 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 sed
die gesamte Datei gelesen wird, während bei durchsuchbaren Dateien tail
das 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 tail
das 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 tail
Aufrufs liegen würde ( Busybox hatte tail
früher diesen Fehler.
So zum Beispiel in:
{ cat; tail -n 1; } < file
Auch wenn tail
man auf die letzte Zeile zurückblicken könnte file
, tut es das nicht. Sein Standard ist ein leerer Stream, der cat
den 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 head
builtin von ksh93
(aktiviert , wenn Sie setzen /opt/ast/bin
voraus $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 .
tail
wahrscheinlich 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,tail
das einfach bis zum Ende der Eingabe durchgesucht werden kann, da es diesen Eingabeversatz mit einem vergleichen muss, mit dem es möglicherweise übereinstimmtlseek()
, und das Ergebnis ist nicht anders alshead -n1 file; tail -n1 file
. Ich finde das Zeug nützlicher, wenn man Eingaben von obenwhile IFS= read -r v; do { printf %s\\n "$v"; head; } >&"$((1+(x=!x)))"; done <in >out1 2>out2