Befehl zum Anzeigen der ersten und letzten Zeilen einer Datei

23

Ich habe eine Datei mit vielen Zeilen, und jede Zeile hat am Anfang einen Zeitstempel wie

[Thread-3] (21/09/12 06:17:38:672) logged message from code.....

Daher überprüfe ich häufig zwei Dinge aus dieser Protokolldatei.

  1. Die ersten paar Zeilen, die die globalen Bedingungen und die Startzeit haben, sind ebenfalls angegeben.
  2. Letzte paar Zeilen, die den Exit-Status mit einigen anderen Informationen haben.

Gibt es einen einfachen Befehl, mit dem ich nur die ersten und letzten Zeilen einer Datei anzeigen kann?

mtk
quelle
2
Was sind die globalen Bedingungen und was head and tailfunktioniert bei Ihnen nicht?
Daisy
Das ist der Teil meiner Protokolldatei. Ich habe versucht, ausführlich zu sein. Sie können das ignorieren.
MTK
Ihre Lösung sieht für mich gut aus. Wenn Sie mehr Komfort wünschen, verwandeln Sie es in eine Shell-Funktion (auch ein Alias ​​könnte dies tun).
Vonbrand
@vonbrand Problem ist, dass ich nicht weißN
Bernhard
@Bernhard, ich bin kein sed(1)Experte, aber es gibt Möglichkeiten, Dinge für die spätere Verwendung zu verstauen. Vielleicht zahlt es sich aus, dort nachzuschauen. OTOH, ich würde wahrscheinlich ein Perl-Skript (oder was auch immer) entwickeln, um dies zu tun, wenn es häufig verwendet wird, da ich damit besser vertraut bin.
Vonbrand

Antworten:

12

Sie können sedoder verwenden awk, um es mit einem Befehl zu machen. Sie verlieren jedoch an Geschwindigkeit, Ursache sedund awkmüssen trotzdem die gesamte Datei durchlaufen. Aus Sicht der Geschwindigkeit ist es viel besser, eine Funktion oder jedes Mal eine Kombination von tail+ zu erstellen head. Dies hat den Nachteil, dass es nicht funktioniert, wenn die Eingabe eine Pipe ist. Sie können jedoch die Prozessersetzung verwenden, falls Ihre Shell dies unterstützt (siehe Beispiel unten).

first_last () {
    head -n 10 -- "$1"
    tail -n 10 -- "$1"
}

und starte es einfach als

first_last "/path/to/file_to_process"

Um mit der Prozessersetzung fortzufahren (nur bash, zsh, ksh wie Shells):

first_last <( command )

ps. Sie können sogar ein hinzufügen grep, um zu überprüfen, ob Ihre "globalen Bedingungen" vorliegen.

eilen
quelle
-n 10ist die Standardeinstellung, nein?
l0b0
@ l0b0 ja, es ist voreingestellt. -n 10ist hier nicht notwendig.
Ansturm
20

Bei @rush ist es richtig, dass head + tail für große Dateien effizienter ist. Bei kleinen Dateien (<20 Zeilen) werden einige Zeilen möglicherweise zweimal ausgegeben.

{ head; tail;} < /path/to/file

wäre genauso effizient, hätte aber das obige Problem nicht.

Stéphane Chazelas
quelle
Im Gegensatz zur Rushs-Lösung funktioniert dies in einer POSIX-Shell nicht.
Marco
2
@ Marco Huh? Hier werden nur POSIX-Konstrukte verwendet. Was siehst du falsch?
Gilles 'SO- hör auf böse zu sein'
2
@ Gilles Ich habe das Leerzeichen verpasst: Funktioniert {head; tail;} < filein zsh, scheitert aber in sh. { head; tail;} < filefunktioniert immer. Entschuldigung für den Lärm.
Marco
@Marco, wenn es Probleme damit gäbe, wäre es mit head, nicht mit der Shell. Bei POSIX muss headder Cursor in der Datei hinter diesen 10 Zeilen für reguläre Dateien bleiben. Ein Problem könnte bei Nicht-POSIX- headImplementierungen auftreten (sehr alte Versionen von GNU head waren in diesem Fall nicht konform, aber wir sprechen von Jahrzehnten) oder wenn die Datei nicht suchbar ist (wie Named Pipe oder Socket, aber dann das andere Lösung hätte das gleiche Problem).
Stéphane Chazelas
1
@FCTW,sudo sh -c '{ head; tail;} < /path/to/file'
Stéphane Chazelas
9

Die { head; tail; }Lösung wäre nicht Arbeit an Rohren (oder Buchsen oder andere nicht-seekable Dateien) , weil headzu viele Daten verbrauchen könnte , wie es durch die Blöcke liest und kann nicht auf einem Rohr suchen zurück möglicherweise in der Datei den Cursor verlassen das hinaus , was tailbedeutet , auswählen.

Sie können also ein Tool verwenden, das wie die Shell jeweils ein Zeichen liest read(hier mit einer Funktion, die die Anzahl der Kopf- und Endzeilen als Argumente verwendet).

head_tail() {
  n=0
  while [ "$n" -lt "$1" ]; do
    IFS= read -r line || { printf %s "$line"; break; }
    printf '%s\n' "$line"
    n=$(($n + 1))
  done
  tail -n "${2-$1}"
}
seq 100 | head_tail 5 10
seq 20 | head_tail 5

oder implementieren Sie tailin awk zum Beispiel als:

head_tail() {
  awk -v h="$1" -v t="${2-$1}" '
    {l[NR%t]=$0}
    NR<=h
    END{
      n=NR-t+1
      if(n <= h) n = h+1
      for (;n<=NR;n++) print l[n%t]
    }'
}

Mit sed:

head_tail() {
  sed -e "1,${1}b" -e :1 -e "$(($1+${2-$1})),\$!{N;b1" -e '}' -e 'N;D'
}

(Beachten Sie jedoch, dass bei einigen sedImplementierungen die Größe des Musterbereichs gering ist, sodass große Werte für die Anzahl der Endzeilen fehlschlagen würden.)

Stéphane Chazelas
quelle
4

Mit der bashProzessersetzung können Sie Folgendes tun:

make_some_output | tee >(tail -n 2) >(head -n 2; cat >/dev/null) >/dev/null

Beachten Sie, dass die Zeilen nicht unbedingt in Ordnung sind, bei Dateien, die länger als ca. 8 KB sind, ist dies jedoch sehr wahrscheinlich. Diese 8-KB-Grenze entspricht der typischen Größe des Lesepuffers und hängt mit dem Grund zusammen, warum | {head; tail;}kleine Dateien nicht funktionieren.

Das cat >/dev/nullist notwendig, um die headPipeline am Leben zu erhalten. Andernfalls teewird das Programm vorzeitig beendet, und während die Ausgabe von erfolgt tail, erfolgt die Ausgabe eher in der Mitte der Eingabe als am Ende.

Zum Schluss, warum, >/dev/nullanstatt tailzu einem anderen zu ziehen |? Im folgenden Fall:

make_some_output | tee >(head -n 2; cat >/dev/null) | tail -n 2  # doesn't work

heads stdout wird taileher in die Pipe als in die Konsole eingespeist , was wir überhaupt nicht wollen.

Jander
quelle
Wenn Kopf oder Schwanz mit dem Schreiben der gewünschten Ausgabe fertig sind, schließen sie ihre Standardeingabe und beenden sie. Von dort kommt die SIGPIPE. Normalerweise ist dies eine gute Sache, da der Rest der Ausgabe verworfen wird. Es gibt also keinen Grund für die andere Seite der Pipe, weiterhin Zeit damit zu verbringen, sie zu generieren.
Derobert
Wodurch wird die Bestellung wahrscheinlich aufrechterhalten? Es wird wahrscheinlich für eine große Datei sein, weil tailsie länger arbeiten muss, aber ich erwarte (und sehe), dass es bei kurzen Eingaben etwa die Hälfte der Zeit fehlschlägt.
Gilles 'SO- hör auf böse zu sein'
Sie erhalten die SIGPIPE mit tee >(head) >(tail)den gleichen Gründen ( >(...)die übrigens eine ksh-Funktion ist, die jetzt sowohl von zsh als auch von bash unterstützt wird), die auch Pipes verwenden. Sie könnten dies tun, ... | (trap '' PIPE; tee >(head) >(tail) > /dev/null)aber es werden immer noch Fehlermeldungen mit unterbrochenen Leitungen angezeigt tee.
Stéphane Chazelas
Auf meinem System (bash 4.2.37, coreutils 8.13) tailwird der von SIGPIPE getötet, nicht teeund tailschreibt nicht in eine Pipe. Also muss es von einem stammen kill(), oder ?. Und das passiert nur, wenn ich die |Syntax verwende. stracesagt, teedas ruft nicht an kill()... also vielleicht bash?
Jander
1
@ Jander, füttere mehr als 8k wieseq 100000 | tee >(head -n1) >(tail -n1) > /dev/null
Stéphane Chazelas
3

Verwenden von ed(wobei die gesamte Datei jedoch in den RAM gelesen wird):

# cf. http://wiki.bash-hackers.org/howto/edit-ed
printf '%s\n' 'H' '1,10p' '$-10,$p' 'q' | ed -s file
Curx
quelle
Kürzere:ed -s file <<< $'11,$-10d\n,p\nq\n'
don_crissti
2

Stephanes erste Lösung in einer Funktion, mit der Sie Argumente verwenden können (funktioniert in jeder Bourne-ähnlichen oder POSIX-Shell):

head_tail() {
    head "$@";
    tail "$@";
}

Jetzt können Sie dies tun:

head_tail -n 5 < /path/to/file

Dies setzt natürlich voraus, dass Sie nur eine Datei betrachten und Stephanes Lösung (zuverlässig) nur für reguläre (durchsuchbare) Dateien funktioniert.

l0b0
quelle
2

Mit der -u( --unbuffered) Option von GNU sedkönnen Sie sed -u 2qals ungepufferte Alternative verwenden head -n2:

$ seq 100|(sed -u 2q;tail -n2)
1
2
99
100

(head -n2;tail -n2)schlägt fehl, wenn die letzten Zeilen Teil des Blocks der Eingabe sind, die verwendet wird von head:

$ seq 1000|(head -n2;tail -n2)
1
2
999
1000
$ seq 100|(head -n2;tail -n2)
1
2
Nisetama
quelle
Das sollte die beste Antwort sein! klappt wunderbar!
Ben Usman
1

Ich bin heute auf so etwas gestoßen, wo ich nur die letzte Zeile und ein paar Zeilen von der Vorderseite eines Baches brauchte und mir die folgenden einfallen ließ.

sed -n -e '1{h}' -e '2,3{H}' -e '${H;x;p}'

Ich las dies wie folgt: Initialisiere den Haltebereich mit dem Inhalt der ersten Zeile, hänge die Zeilen 2-3 an den Haltebereich an, füge bei EOF die letzte Zeile an den Haltebereich an, tausche den Halte- und Musterbereich aus und drucke das Muster Platz.

Vielleicht kann jemand mit mehr sed-fu als ich herausfinden, wie man dies verallgemeinert, um die letzten paar Zeilen des in dieser Frage angegebenen Streams zu drucken , aber ich brauchte es nicht und konnte keinen einfachen Weg finden, basierend auf der $Adresse zu rechnen in sedoder vielleicht durch Verwalten des Laderaums, so dass nur die letzten Zeilen darin sind, wenn EOFerreicht wird.

taub
quelle
1

Sie können Perl ausprobieren, wenn Sie es installiert haben:

perl -e '@_ = <>; @_=@_[0, -3..-1]; print @_'

Dies funktioniert für die meisten Dateien, liest jedoch die gesamte Datei in den Speicher, bevor sie verarbeitet wird. Wenn Sie mit Perl-Slices nicht vertraut sind, bedeutet "0" in eckigen Klammern "Erste Zeile" und "-3 ...- 1" "Letzte drei Zeilen". Sie können beide auf Ihre Bedürfnisse zuschneiden. Wenn Sie sehr große Dateien verarbeiten müssen (was "groß" ist, hängt möglicherweise von Ihrem Arbeitsspeicher ab und möglicherweise von der Größe der Auslagerungsdateien), können Sie Folgendes auswählen:

perl -e 'while($_=<>){@_=(@_,$_)[0,-3..-1]}; print @_'

Es kann etwas langsamer sein, da bei jeder Iteration ein Slice erstellt wird, dies ist jedoch unabhängig von der Dateigröße.

Beide Befehle sollten sowohl in Pipes als auch mit regulären Dateien funktionieren.

Jasio
quelle