Die Verwendung in tail
Kombination 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, tail
versagt Ausgabe zu erzeugen, weil ein Rohr ist un- lseek der Lage :
$ seq 10 | { head -n1; tail -n1; }
1
Wenn der Inhalt groß genug ist, tail
funktioniert Folgendes:
$ seq 10000 | { head -n1; tail -n1; }
1
10000
Das liegt daran, dass nach dem ersten lseek
Fehler bekannt ist, tail
dass 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 tail
und die lseek
Dokumentation 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.
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
tail
zumindest gruppieren , sollte es keine Überlappung drucken, also gibt es das natürlich. Entschuldigen Sie.Antworten:
Beachten Sie, dass das Problem nicht bei,
tail
sondern beihead
hier liegt, das mehr aus der Pipe liest als die erste Zeile, die ausgegeben werden soll (es bleibt also nichts mehrtail
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 :
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 dieread
oderline
Dienstprogramm tun oder GNUsed
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:
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 Dateientail
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, dasstail
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 destail
Aufrufs liegen würde ( Busybox hattetail
früher diesen Fehler.So zum Beispiel in:
Auch wenntail
man auf die letzte Zeile zurückblicken könntefile
, tut es das nicht. Sein Standard ist ein leerer Stream, dercat
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 vonksh93
(aktiviert , wenn Sie setzen/opt/ast/bin
voraus$PATH
), für Buchsen (eine Art von nicht-seekable Dateien) anstelle lugt am Eingang (mitrecvfrom(..., 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 mitsocketpair()
s anstelle von implementiertpipe()
. 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 .quelle
tail
Verhalten wissen . Nachdem der erste fehlgeschlagen warlseek
, begann er, den Inhalt zu lesen. Während des Aufenthaltsseq 10000 | tail -n1
versuchte es nicht einmal, irgendwelche auszuführenlseek
. Hat POSIX dastail
Verhalten definiert , wenn eslseek
fehlschlägt?seq 10 | { head -n1; tail -n1; }
,tail
ist mit nichts mehr übrig , weil zu lesenhead
hat gierig den Eingang schlürfte auf. In jedem Fall wäre es nicht sinnvolltail
, einelseek
nicht durchsuchbare Datei zu versuchenfstat(0)
). Siehe auch meine Bearbeitung.lseek
POSIX es zulässt , wenn der erste Versuch fehlschlug, den Versuchtail
zu verwerfenlseek
und sein Verhalten zu ändern, um die Datei bis zum Ende zu lesen?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 GNUtail
versucht, geschweige denn fehlzuschlagen,lseek
wenn die Eingabe eine Pipe ist.