Verwenden von Kopf und Schwanz zum Erfassen verschiedener Zeilensätze und Speichern in derselben Datei

Verwenden von Kopf und Schwanz zum Erfassen verschiedener Zeilensätze und Speichern in derselben Datei

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 +NArgument tailnicht 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. tailLiest also einige Kilobyte, überspringt am Anfang ein bisschen, übergibt ein bisschen mehr an headund stoppt – aber was gelesen wurde, ist gelesen und steht für den nächsten Befehl nicht zur Verfügung.

Ein anderer Ansatzist die Verwendung headvon Piped zu/dev/nullum 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 headBefehl 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 headgelesen 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.

verwandte Informationen