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.

toop
quelle
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.

Aleksandra Zalcman
quelle
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
kev
quelle
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;'
RantingNerd
quelle
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.

Gast
quelle
4

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

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

mah
quelle
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
modular
quelle
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
Samus_
quelle
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.
Sorin
quelle
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] }' ; }
Michael Blahay
quelle
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.

SRI
quelle
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

Paul
quelle
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

Ich habe dazu eine einfache Python-App geschrieben: https://gist.github.com/garyvdm/9970522

Es behandelt sowohl Pipes (Streams) als auch Dateien.

Gary van der Merwe
quelle
2
Am besten posten Sie die relevanten Teile des Codes.
Fedorqui 'SO hör auf,'
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
zzapper
quelle
0

Warum nicht sedfür diese Aufgabe verwenden?

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

lik
quelle
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.

Camille Goudeseune
quelle
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
Drehbuch Wolf
quelle
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

mariana.ft
quelle
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 .

mark_infinite
quelle
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
Jitsusama
quelle
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
Brad Parks
quelle