Unix - Kopf UND Ende der Datei


131

Angenommen, Sie haben eine txt-Datei. Wie lautet der Befehl, um die oberen 10 Zeilen und die unteren 10 Zeilen der Datei gleichzeitig anzuzeigen?

Wenn die Datei 200 Zeilen lang ist, können Sie die Zeilen 1-10 und 190-200 auf einmal anzeigen.


Was meinst du mit "auf einmal"?
cnicutar

@cnicutar dh. nicht gehen Kopf -10 Datei mit Blick auf die Daten und dann separat gehen Schwanz -10 Datei und Blick auf die Daten
toop

@toop Wenn Sie ein echtes Arbeitsbeispiel wollen, siehe stackoverflow.com/a/44849814/99834
sorin

Antworten:


208

Sie können einfach:

(head; tail) < file.txt

Und wenn Sie aus irgendeinem Grund Rohre verwenden müssen, dann wie folgt:

cat file.txt | (head; tail)

Hinweis: Gibt doppelte Zeilen aus, wenn die Anzahl der Zeilen in file.txt kleiner ist als die Standardzeilen des Kopfes + die Standardzeilen des Endes.


54
Genau genommen erhalten Sie nicht das Ende der Originaldatei, sondern das Ende des Streams, nachdem headdie ersten 10 Zeilen der Datei verbraucht wurden. (Vergleichen Sie dies mit head < file.txt; tail < file.txteiner Datei mit weniger als 20 Zeilen). Nur ein sehr kleiner Punkt zu beachten. (Aber immer noch +1.)
Chepper

15
Nett. Wenn Sie eine Lücke zwischen den Kopf- und Schwanzteilen wünschen: (Kopf; Echo; Schwanz) <file.txt
Simon Hibbs

3
Neugierig, warum / wie das funktioniert. Als neue Frage gestellt: stackoverflow.com/questions/13718242
zellyn

9
@nametal Eigentlich bekommst du vielleicht gar nicht so viel. Während headnur die ersten 10 Zeilen der Eingabe angezeigt werden, kann nicht garantiert werden, dass nicht mehr davon verbraucht wurden, um das Ende der 10. Zeile zu finden, sodass weniger Eingabe für lessdie Anzeige übrig bleibt .
Chepper

20
Tut mir leid zu sagen, aber die Antwort funktioniert nur in einigen Fällen. seq 100 | (head; tail)gibt mir nur die ersten 10 Zahlen. Nur bei viel größeren Eingaben (wie seq 2000) erhält der Schwanz eine Eingabe.
Modular

18

ed ist der standard text editor

$ echo -e '1+10,$-10d\n%p' | ed -s file.txt

2
Was ist, wenn die Datei mehr oder weniger als 200 Zeilen enthält? Und Sie kennen die Anzahl der Zeilen von Anfang an nicht?
Paul

@ Paul Ich habe geändert sedzued
kev

14

Für einen reinen Stream (z. B. Ausgabe eines Befehls) können Sie 'tee' verwenden, um den Stream zu teilen und einen Stream an head und einen an tail zu senden. Dies erfordert die Verwendung der Funktion '> (Liste)' von bash (+ / dev / fd / N):

( COMMAND | tee /dev/fd/3 | head ) 3> >( tail )

oder mit / dev / fd / N (oder / dev / stderr) plus Subshells mit komplizierter Umleitung:

( ( seq 1 100 | tee /dev/fd/2 | head 1>&3 ) 2>&1 | tail ) 3>&1
( ( seq 1 100 | tee /dev/stderr | head 1>&3 ) 2>&1 | tail ) 3>&1

(Beides funktioniert nicht in csh oder tcsh.)

Für etwas mit etwas besserer Kontrolle können Sie diesen Perl-Befehl verwenden:

COMMAND | perl -e 'my $size = 10; my @buf = (); while (<>) { print if $. <= $size; push(@buf, $_); if ( @buf > $size ) { shift(@buf); } } print "------\n"; print @buf;'

1
+1 für Stream-Unterstützung. Sie könnten stderr wiederverwenden:COMMAND | { tee >(head >&2) | tail; } |& other_commands
jfs

2
Übrigens wird es für Dateien unterbrochen, die größer als die Puffergröße sind (8 KB auf meinem System). cat >/dev/nullbehebt es:COMMAND | { tee >(head >&2; cat >/dev/null) | tail; } |& other_commands
jfs

Ich war begeistert von der Lösung, aber nach dem Spiel für aa während ich bemerkte , dass der Schwanz in einigen Fällen vor dem Kopf hatte ... Es sind keine Bestellung garantiert zwischen headund tailBefehle: \ ...
Jan

7
(sed -u 10q; echo ...; tail) < file.txt

Nur eine weitere Variation des (head;tail)Themas, aber Vermeidung des anfänglichen Problems der Pufferfüllung für kleine Dateien.


4

head -10 file.txt; tail -10 file.txt

Ansonsten müssen Sie Ihr eigenes Programm / Skript schreiben.


1
Schön, ich habe immer catund headoder tailgepfeift, gut zu wissen, dass ich sie einzeln verwenden kann!
Paul

Wie kann ich dann diese ersten 10 + letzten 10 in einen anderen Befehl weiterleiten?
Toop

1
@Paul - mit 'your_program' als wc -l gibt es 10 statt 20 zurück
toop

3
oder, ohne eine Unterschale erzeugen zu müssen: { head file; tail file; } | prog(Abstand innerhalb der Klammern und das
nachfolgende

1
Wow ... eine Abwertung für eine Antwort, die anderen sehr ähnlich ist (aber vor ihnen mit einem Zeitstempel versehen ist), von jemandem, der sich entschieden hat, nicht zu posten, warum er abgewählt hat. Nett!
Mah

4

Basierend auf dem Kommentar von JF Sebastian :

cat file | { tee >(head >&3; cat >/dev/null) | tail; } 3>&1

Auf diese Weise können Sie die erste Zeile und den Rest in einer Pipe unterschiedlich verarbeiten. Dies ist nützlich für die Arbeit mit CSV-Daten:

{ echo N; seq 3;} | { tee >(head -n1 | sed 's/$/*2/' >&3; cat >/dev/null) | tail -n+2 | awk '{print $1*2}'; } 3>&1
N * 2
2
4
6

3

Das Problem hierbei ist, dass Stream-orientierte Programme die Länge der Datei nicht im Voraus kennen (da es möglicherweise keine gibt, wenn es sich um einen echten Stream handelt).

Tools wie tailPuffern Sie die letzten n Zeilen und warten Sie auf das Ende des Streams. Drucken Sie dann.

Wenn Sie dies in einem einzigen Befehl tun möchten (und es mit einem beliebigen Versatz arbeiten lassen und keine Zeilen wiederholen, wenn sie sich überschneiden), müssen Sie dieses erwähnte Verhalten emulieren.

versuche das awk:

awk -v offset=10 '{ if (NR <= offset) print; else { a[NR] = $0; delete a[NR-offset] } } END { for (i=NR-offset+1; i<=NR; i++) print a[i] }' yourfile

Es braucht mehr Arbeit, um Probleme zu vermeiden, wenn der Versatz größer als die Datei ist
Samus_

Ja, dies funktioniert mit Piped-Ausgabe, nicht nur Dateien: a.out | awk -v ...
Camille Goudeseune

in der Tat :) aber das ist das normale Verhalten von awk. Die meisten Befehlszeilenprogramme arbeiten mit stdin, wenn sie ohne Argumente aufgerufen werden.
Samus_

1
Sehr nahe am gewünschten Verhalten, aber es scheint, dass für <10 Zeilen zusätzliche neue Zeilen hinzugefügt werden.
Sorin

3

Es hat viel Zeit gekostet, diese Lösung zu finden, die (bisher) die einzige zu sein scheint, die alle Anwendungsfälle abdeckt:

command | tee full.log | stdbuf -i0 -o0 -e0 awk -v offset=${MAX_LINES:-200} \
          '{
               if (NR <= offset) print;
               else {
                   a[NR] = $0;
                   delete a[NR-offset];
                   printf "." > "/dev/stderr"
                   }
           }
           END {
             print "" > "/dev/stderr";
             for(i=NR-offset+1 > offset ? NR-offset+1: offset+1 ;i<=NR;i++)
             { print a[i]}
           }'

Funktionsliste:

  • Live-Ausgabe für Kopf (offensichtlich ist dies für Schwanz nicht möglich)
  • Keine Verwendung externer Dateien
  • Fortschrittsbalken ein Punkt für jede Zeile nach MAX_LINES, sehr nützlich für lange laufende Aufgaben.
  • Fortschrittsbalken auf stderr, um sicherzustellen, dass die Fortschrittspunkte von Kopf + Schwanz getrennt sind (sehr praktisch, wenn Sie stdout leiten möchten)
  • vermeidet mögliche falsche Protokollierungsreihenfolge aufgrund von Pufferung (stdbuf)
  • Vermeiden Sie doppelte Ausgaben, wenn die Gesamtzahl der Zeilen kleiner als Kopf + Schwanz ist.

2

Ich habe eine Weile nach dieser Lösung gesucht. Ich habe es selbst mit sed versucht, aber das Problem, die Länge der Datei / des Streams vorher nicht zu kennen, war unüberwindbar. Von allen oben verfügbaren Optionen gefällt mir die awk-Lösung von Camille Goudeseune. Er machte sich eine Notiz, dass seine Lösung zusätzliche Leerzeilen in der Ausgabe mit einem ausreichend kleinen Datensatz hinterließ. Hier stelle ich eine Modifikation seiner Lösung zur Verfügung, die die zusätzlichen Zeilen entfernt.

headtail() { awk -v offset="$1" '{ if (NR <= offset) print; else { a[NR] = $0; delete a[NR-offset] } } END { a_count=0; for (i in a) {a_count++}; for (i=NR-a_count+1; i<=NR; i++) print a[i] }' ; }

1

Nun, Sie können sie immer miteinander verketten. Wie so , head fiename_foo && tail filename_foo. Wenn dies nicht ausreicht, können Sie sich eine Bash-Funktion in Ihre .profile-Datei oder eine von Ihnen verwendete Anmeldedatei schreiben:

head_and_tail() {
    head $1 && tail $1
}

Rufen Sie es später über Ihre Shell-Eingabeaufforderung auf : head_and_tail filename_foo.


1

Erste 10 Zeilen von file.ext, dann die letzten 10 Zeilen:

cat file.ext | head -10 && cat file.ext | tail -10

Letzte 10 Zeilen der Datei, dann die ersten 10:

cat file.ext | tail -10 && cat file.ext | head -10

Sie können die Ausgabe dann auch an eine andere Stelle weiterleiten:

(cat file.ext | head -10 && cat file.ext | tail -10 ) | your_program


5
Warum cat verwenden, wenn Sie nur head -10 file.txt aufrufen können?
Jstarek

Können Sie die Anzahl der Zeilen variabel machen, sodass der Aufruf ungefähr so ​​lautet: head_ tail (foo, m, n) - Rückgabe der ersten m und der letzten n Textzeilen?
Ricardo

@ricardo, bei dem ein Bash-Skript geschrieben wird, das 3 Argumente benötigt und diese durch Aliasing an tailund / headoder eine Funktion übergibt .
Paul


1

auf die obigen Ideen zurückgreifen (getestete bash & zsh)

aber mit einem Alias ​​"Hut" Kopf und Schwanz

alias hat='(head -5 && echo "^^^------vvv" && tail -5) < '


hat large.sql

0

Warum nicht sedfür diese Aufgabe verwenden?

sed -n -e 1,+9p -e 190,+9p textfile.txt


3
Dies funktioniert für Dateien bekannter Länge, jedoch nicht für Dateien, deren Länge unbekannt ist.
Kevin

0

Fügen Sie Folgendes zu Ihrer .bashrc- oder .profile-Datei hinzu, um Pipes (Streams) sowie Dateien zu verarbeiten:

headtail() { awk -v offset="$1" '{ if (NR <= offset) print; else { a[NR] = $0; delete a[NR-offset] } } END { for (i=NR-offset+1; i<=NR; i++) print a[i] }' ; }

Dann kannst du nicht nur

headtail 10 < file.txt

aber auch

a.out | headtail 10

(Dies fügt immer noch unechte Leerzeilen hinzu, wenn 10 die Länge der Eingabe überschreitet, im Gegensatz zu einfach alt a.out | (head; tail). Vielen Dank, frühere Antwortende.)

Hinweis: headtail 10nicht headtail -10.


0

Aufbauend auf was @Samus_ erklärt hier , wie @Aleksandra Zalcman der Befehl funktioniert, ist diese Variante praktisch , wenn Sie nicht schnell erkennen können , wo der Schwanz ohne Zähllinien beginnt.

{ head; echo "####################\n...\n####################"; tail; } < file.txt

Wenn Sie mit etwas anderem als 20 Zeilen arbeiten, kann eine Zeilenanzahl sogar hilfreich sein.

{ head -n 18; tail -n 14; } < file.txt | cat -n

0

Gehen Sie folgendermaßen vor, um die ersten 10 und letzten 10 Zeilen einer Datei zu drucken:

cat <(head -n10 file.txt) <(tail -n10 file.txt) | less


0
sed -n "1,10p; $(( $(wc -l ${aFile} | grep -oE "^[[:digit:]]+")-9 )),\$p" "${aFile}"

HINWEIS : Die Variable aFile enthält den vollständigen Pfad der Datei .


0

Ich würde sagen, dass es abhängig von der Größe der Datei möglicherweise nicht wünschenswert ist, den Inhalt aktiv einzulesen. Unter diesen Umständen denke ich, dass ein einfaches Shell-Scripting ausreichen sollte.

So habe ich dies kürzlich für eine Reihe sehr großer CSV-Dateien gehandhabt, die ich analysiert habe:

$ for file in *.csv; do echo "### ${file}" && head ${file} && echo ... && tail ${file} && echo; done

Dies druckt die ersten 10 Zeilen und die letzten 10 Zeilen jeder Datei aus, während gleichzeitig der Dateiname und einige Auslassungspunkte vorher und nachher ausgedruckt werden.

Für eine einzelne große Datei können Sie einfach Folgendes ausführen, um den gleichen Effekt zu erzielen:

$ head somefile.csv && echo ... && tail somefile.csv

0

Verbraucht Standard, aber einfach und funktioniert für 99% der Anwendungsfälle

head_and_tail

#!/usr/bin/env bash
COUNT=${1:-10}
IT=$(cat /dev/stdin)
echo "$IT" | head -n$COUNT
echo "..."
echo "$IT" | tail -n$COUNT

Beispiel

$ seq 100 | head_and_tail 4
1
2
3
4
...
97
98
99
100
Durch die Nutzung unserer Website bestätigen Sie, dass Sie unsere Cookie-Richtlinie und Datenschutzrichtlinie gelesen und verstanden haben.
Licensed under cc by-sa 3.0 with attribution required.