Dies ist also eine Hausaufgabe, aber ich werde nicht die konkrete Hausaufgabefrage stellen.
Ich muss head und tail verwenden, um verschiedene Zeilensätze aus einer Datei zu erfassen. Also etwa die Zeilen 6-11 und 19-24 und beide in einer anderen Datei speichern. Ich weiß, dass ich das mit append machen kann, wie zum Beispiel
head -11 file|tail -6 > file1; head -24 file| tail -6 >> file1.
Aber ich glaube nicht, dass wir das tun sollen.
Gibt es eine bestimmte Möglichkeit, die Head- und Tail-Befehle zu kombinieren und dann in der Datei zu speichern?
Antwort1
Sie können dies allein mit einfacher Arithmetik tun head
, wenn Sie Befehle mit { ... ; }
einer Konstruktion wie
{ head -n ...; head -n ...; ...; } < input_file > output_file
wo alle Befehle die gleiche Eingabe teilen (dank@mikeserv).
Die Zeilen 6-11 und 19-24 zu erhalten ist gleichbedeutend mit:
head -n 5 >/dev/null # dump the first 5 lines to `/dev/null` then
head -n 6 # print the next 6 lines (i.e. from 6 to 11) then
head -n 7 >/dev/null # dump the next 7 lines to `/dev/null` ( from 12 to 18)
head -n 6 # then print the next 6 lines (19 up to 24)
Grundsätzlich würden Sie also Folgendes ausführen:
{ head -n 5 >/dev/null; head -n 6; head -n 7 >/dev/null; head -n 6; } < input_file > output_file
Antwort2
Sie können die { … }
Gruppierungskonstrukt verwenden, um den Umleitungsoperator auf einen zusammengesetzten Befehl anzuwenden.
{ head -n 11 file | tail -n 6; head -n 24 file | tail -n 6; } >file1
Anstatt die ersten M+N Zeilen zu duplizieren und nur die letzten N zu behalten, können Sie die ersten M Zeilen überspringen und die nächsten N duplizieren. Dies istmessbar schneller bei großen Dateien. Beachten Sie, dass das +N
Argument tail
nicht die Anzahl der zu überspringenden Zeilen ist, sondern eins plus diese Zahl – es ist die Nummer der ersten zu druckenden Zeile, wobei die Zeilen ab 1 nummeriert sind.
{ tail -n +6 file | head -n 6; tail -n +19 file | head -n 6; } >file1
In beiden Fällen wird die Ausgabedatei nur einmal geöffnet, die Eingabedatei jedoch einmal durchsucht, um jedes Snippet zu extrahieren. Wie wäre es mit einer Gruppierung der Eingaben?
{ tail -n +6 | head -n 6; tail -n +14 | head -n 6; } <file >file1
Im Allgemeinen funktioniert das nicht. (Auf manchen Systemen funktioniert es vielleicht, zumindest wenn die Eingabe eine normale Datei ist.) Warum? WegenEingabepufferung. Die meisten Programme, einschließlich tail
, lesen ihre Eingabe nicht Byte für Byte, sondern einige Kilobyte auf einmal, weil das schneller ist. tail
Liest also einige Kilobyte, überspringt am Anfang ein bisschen, übergibt ein bisschen mehr an head
und stoppt – aber was gelesen wurde, ist gelesen und steht für den nächsten Befehl nicht zur Verfügung.
Ein anderer Ansatzist die Verwendung head
von Piped zu/dev/null
um Zeilen zu überspringen.
{ head -n 5 >/dev/null; head -n 6; head -n 7 >/dev/null; head -n 6; } <file >file1
Auch hier gibt es aufgrund der Pufferung keine Garantie, dass dies funktioniert. Es funktioniert zufällig mit dem head
Befehl von GNU coreutils (der auf nicht eingebetteten Linux-Systemen zu finden ist), wenn die Eingabe aus einer regulären Datei stammt. Das liegt daran, dass diese Implementierung von, sobald sie head
gelesen hat, was sie will,legt die Dateiposition festzum ersten Byte, das nicht ausgegeben wurde. Dies funktioniert nicht, wenn die Eingabe eine Pipe ist.
Eine einfachere Möglichkeit, mehrere Zeilenfolgen aus einer Datei auszudrucken, besteht darin, ein allgemeineres Werkzeug wiesedoderawk. (Das kann langsamer sein, ist aber nur bei extrem großen Dateien von Bedeutung.)
sed -n -e '6,11p' -e '19,24p' <file >file1
sed -e '1,5d' -e '12,18d' -e '24q' <file >file1
awk '6<=NR && NR<=11 || 19<=NR && NR<=24' <file >file1
awk 'NR==6, NR==11; NR==19, NR==24' <file >file1
Antwort3
Ich weiß, Sie sagten, Sie müssen Kopf und Schwanz verwenden, aber sed ist hier definitiv das einfachere Tool für diese Aufgabe.
$ cat foo
a 1 1
a 2 1
b 1 1
a 3 1
c 3 1
c 3 1
$ sed -ne '2,4p;6p' foo
a 2 1
b 1 1
a 3 1
c 3 1
Sie können die Blöcke sogar mit einem anderen Prozess in einer Zeichenfolge erstellen und diesen über sed ausführen.
$ a="2,4p;6p"
$ sed -ne $a foo
a 2 1
b 1 1
a 3 1
c 3 1
-n negiert die Ausgabe, dann geben Sie mit p die zu druckenden Bereiche an, wobei die erste und die letzte Zahl des Bereichs durch ein Komma getrennt sind.
Davon abgesehen können Sie entweder die von @don_crissti vorgeschlagene Befehlsgruppierung durchführen oder die Datei mehrmals durchlaufen, wobei head/tail bei jedem Durchlauf einen Zeilenabschnitt erfassen.
$ head -4 foo | tail -3; head -6 foo | tail -1
a 2 1
b 1 1
a 3 1
c 3 1
Je mehr Zeilen eine Datei enthält und je mehr Blöcke Sie haben, desto effizienter wird sed.
Antwort4
Verwenden Sie eine Bash-Funktion wie diese:
seq 1 30 > input.txt
f(){ head $1 input.txt | tail $2 >> output.txt ;}; f -11 -2; f -24 -3
cat output.txt
10
11
22
23
24
Das ist in diesem Fall zwar etwas übertrieben, kann aber ein Segen sein, wenn Ihre Filter größer werden.