Verwenden Sie Kopf und Schwanz, um verschiedene Liniensätze zu erfassen und in derselben Datei zu speichern

10

Das ist also für Hausaufgaben, aber ich werde nicht die spezifische Hausaufgabenfrage stellen.

Ich muss Kopf und Schwanz verwenden, um verschiedene Liniensätze aus einer Datei zu ziehen. Also wie die Zeilen 6-11 und 19-24 und speichern Sie beide in einer anderen Datei. Ich weiß, dass ich dies mit Anhängen wie tun kann

head -11 file|tail -6 > file1; head -24 file| tail -6 >> file1. 

Aber ich denke nicht, dass wir das sollen.
Gibt es eine bestimmte Möglichkeit, die Head- und Tail-Befehle zu kombinieren und dann in der Datei zu speichern?

user2709291
quelle
1
Sind sie fragen Sie gezielt zu nutzen headund tail? Wenn ja, ist Ihre Lösung so ziemlich das Beste, was Sie tun können. Wenn Sie andere Programme verwenden dürfen sedoder awkmöglicherweise bessere Lösungen zulassen (dh mit weniger Prozessaufrufen).
n.st
Ja, sie bitten uns, Kopf und Schwanz zu benutzen. Vielen Dank für Ihre Antwort.
user2709291
Eine weitere Sache, die ich hinzufügen kann: Sie können die anhängende Ausgabeumleitung ( >>) umgehen, indem Sie die beiden Befehle in Klammern setzen, um ihre verkettete Ausgabe umzuleiten : (head -11 file | tail -6; head -24 file | tail -6) > file1. Es kommt wirklich auf die persönlichen Vorlieben an, was schöner ist.
n.st
Danke, das wird sehr gut gehen. Ich weiß das wirklich zu schätzen.
user2709291

Antworten:

11

Sie können dies mit headalleiniger und grundlegender Arithmetik tun , wenn Sie Befehle mit { ... ; }einem Konstrukt wie gruppieren

{ head -n ...; head -n ...; ...; } < input_file > output_file

wo alle Befehle die gleiche Eingabe teilen (danke @mikeserv ).
Das Abrufen der Zeilen 6-11 und 19-24 entspricht:

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)

Im Grunde würden Sie also laufen:

{ head -n 5 >/dev/null; head -n 6; head -n 7 >/dev/null; head -n 6; } < input_file > output_file
don_crissti
quelle
6

Mit dem { … }Gruppierungskonstrukt können Sie den Umleitungsoperator auf einen zusammengesetzten Befehl anwenden.

{ 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 beizubehalten, können Sie die ersten M Zeilen überspringen und die nächsten N duplizieren. Dies ist bei großen Dateien messbar schneller . Beachten Sie, dass das +NArgument von tailnicht die Anzahl der zu überspringenden Zeilen ist, sondern eins plus das - es ist die Nummer der ersten zu druckenden Zeile mit Zeilen, die von 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 wird jedoch einmal durchlaufen, damit jedes Snippet extrahiert werden kann. Wie wäre es mit der Gruppierung der Eingaben?

{ tail -n +6 | head -n 6; tail -n +14 | head -n 6; } <file >file1

Im Allgemeinen funktioniert dies nicht. (Auf einigen Systemen funktioniert dies möglicherweise, zumindest wenn es sich bei der Eingabe um eine reguläre Datei handelt.) Warum? Wegen der Eingabepufferung . Die meisten Programme, einschließlich tail, lesen ihre Eingabe nicht byteweise, sondern jeweils einige Kilobyte, weil sie schneller sind. So tailliest ein paar Kilobyte, springt ein wenig am Anfang, geht ein bisschen mehr head, und stoppt - aber was gelesen wird , gelesen wird , und ist für den nächsten Befehl nicht zur Verfügung.

Ein anderer Ansatz ist die Verwendung von headPiped /dev/nullzum Überspringen von Zeilen.

{ head -n 5 >/dev/null; head -n 6; head -n 7 >/dev/null; head -n 6; } <file >file1

Auch dies kann aufgrund der Pufferung nicht garantiert funktionieren. 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, die Dateiposition auf das erste Byte setzt, das sie nicht ausgegeben hat. Dies funktioniert nicht, wenn die Eingabe eine Pipe ist.

Eine einfachere Möglichkeit, mehrere Zeilenfolgen aus einer Datei zu drucken, besteht darin, ein allgemeineres Tool wie sed oder awk aufzurufen . (Dies kann langsamer sein, ist jedoch nur für extrem große 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
Gilles 'SO - hör auf böse zu sein'
quelle
2
Es funktioniert nicht , es ist ein standardmäßiges, spezifiziertes Verhalten - obwohl eine Pipe, wie Sie sagen, sicherlich keine zuverlässige Eingabequelle für gemeinsam genutzte Eingaben ist. STANDARDBESCHREIBUNG STANDARD : Wenn ein Standarddienstprogramm eine durchsuchbare Eingabedatei liest und fehlerfrei beendet wird, bevor es das Dateiende erreicht, stellt das Dienstprogramm sicher, dass der Dateiversatz in der Beschreibung der geöffneten Datei direkt nach dem letzten von verarbeiteten Byte richtig positioniert ist das Dienstprogramm.
Mikesserv
2

Ich weiß, dass Sie gesagt haben, dass Sie Kopf und Schwanz verwenden müssen, aber sed ist definitiv das einfachere Werkzeug für den Job hier.

$ 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 diese durch 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 Bereiche an, die mit p gedruckt werden sollen, wobei die erste und letzte Nummer des Bereichs durch ein Komma getrennt sind.

Davon abgesehen können Sie entweder die von @don_crissti vorgeschlagene Befehlsgruppierung ausführen oder die Datei einige Male durchlaufen, wobei Kopf / Schwanz bei jedem Durchlauf einen Teil der Zeilen greifen.

$ 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 in einer Datei und je mehr Blöcke Sie haben, desto effizienter wird sed.

Falsche Namen
quelle
2

Mit sedkönnten Sie tun:

sed '24q;1,5d;12,18d' <infile >outfile

... Möglicherweise könnte eine effizientere Lösung mit gehabt werden head. Don hat bereits gezeigt, wie das sehr gut funktionieren könnte, aber ich habe auch damit herumgespielt. Etwas, das Sie tun könnten, um diesen speziellen Fall zu behandeln:

for   n in 5 6 7 6
do    head -n"$n" >&"$((1+n%2))"
done  <infile >outfile 2>/dev/null

... die headviermal aufschreiben würde outfile, /dev/nullje nachdem, ob der Wert dieser Iteration $neine gerade oder eine ungerade Zahl ist.

Für allgemeinere Fälle habe ich dies aus einigen anderen Sachen zusammengeschustert, die ich bereits hatte:

somehead()( 
### call it like:
### somehead -[repeat] [-][numlines]* <infile >outfile
    set -e -- "${1#-}" "$@"                             #-e for arg validation
    r=; cd -- "${TMP:-/tmp}"                            #go to tmp
    dd bs=4096 of="$$$$" <&4 2>&3 &                     #dd <in >tmpfile &bg
    until [ -s "$$$$" ]; do :; done                     #wait while tmpfile empty
    exec <"$$$$" 4<&-;   rm "$$$$"                      #<tmpfile; rm tmpfile
    [ "$3${1}0" -ne "$3${2#?}0" ]          ||           #validate args - chk $1
            shift "$(((r=-${1:--1})||1))"; shift        #shift 1||2
    while [ "$(((r+=(_n=1))-1))" -ne 0 ]   &&           #while ! $rptmax &&
          IFS= read -r l                   &&           #      ! EOF     &&
          printf "%.$(($1>0?${#l}+1:0))s" "$l           #      ? printf  do
";  do    for n do [ "${n#-}" -gt 0 ]      || exit      #args all -[nums>0]
          head "-n$((${n#-}-_n))" >&"$((n>(_n=0)?1:3))" #head -n?$1 >?[+-]
    done; done                                          #done and done
)   4<&0 3>/dev/null                                    #4<for dd 3>for head

Dies kann dein Ding machen wie:

 seq 100 | somehead -1 -5 6 -7 6

... was druckt ...

6
7
8
9
10
11
19
20
21
22
23
24

Es erwartet, dass sein erstes Argument eine Wiederholungszahl ist -, der ein oder nur ein a vorangestellt ist -. Wenn eine Zählung angegeben wird, wird das in den folgenden Argumenten angegebene Linienmuster so oft wie angegeben wiederholt und angehalten, sobald dies geschehen ist.

Für jedes folgende Argument wird eine negative Ganzzahl interpretiert, um eine Zeilenanzahl anzugeben, in die geschrieben werden soll, /dev/nullund eine positive Ganzzahl, um eine Zeilenanzahl anzugeben, in die geschrieben werden soll stdout.

Im obigen Beispiel werden also die ersten 5 Zeilen an /dev/null, die nächsten 6 an stdout, die nächsten 7 an /dev/nullund die nächsten 6 erneut an gedruckt stdout. Nachdem es das letzte seiner Argumente erreicht und die -1Wiederholungszählung vollständig durchlaufen hat , wird es beendet. Wenn das erste Argument gewesen wäre -2, hätte es den Vorgang noch einmal wiederholt, oder wenn es -so lange wie möglich gewesen wäre.

Für jeden Arg-Zyklus wird die whileSchleife einmal durchlaufen. Am Anfang jeder Schleife wird die erste Zeile von stdinin die Shell-Variable eingelesen $l. Dies ist notwendig, da while head </dev/null; do :; donees auf unbestimmte Zeit wiederholt wird - headzeigt in seiner Rückgabe an, wann das Dateiende erreicht ist. Die Prüfung gegen EOF ist also nur dann gewidmet readund printfwird $lplus eine neue Zeile geschrieben, stdoutwenn das zweite Argument eine positive ganze Zahl ist.

Die readPrüfung verkompliziert die Schleife ein wenig, da unmittelbar nach dem Aufruf einer anderen Schleife eine forSchleife über Argumente iteriert, 2-$#wie in $nfür jede Iteration ihrer übergeordneten whileSchleife dargestellt. Dies bedeutet, dass für jede Iteration das erste Argument von dem in der Befehlszeile angegebenen Wert um eins dekrementiert werden muss, alle anderen jedoch ihre ursprünglichen Werte beibehalten sollten. Daher wird der Wert des $_nMarkers var von jedem subtrahiert, enthält jedoch immer nur a Wert größer als 0 für das erste Argument.

Dies stellt die Hauptschleife der Funktion dar, aber der Großteil des Codes befindet sich oben und soll es der Funktion ermöglichen, selbst eine Pipe als Eingabe sauber zu puffern. Dies funktioniert, indem zuerst ein Hintergrundbild ddaufgerufen wird, um es bei der Ausgabe in Blockgrößen von 4 KB pro Stück in ein tmpfile zu kopieren. Die Funktion richtet dann eine Halteschleife ein, die fast nie einen einzigen vollständigen Zyklus abschließen sollte, um sicherzustellen, dass ddmindestens ein einziger Schreibvorgang in die Datei ausgeführt wurde, bevor die Funktion ihren Standard durch einen Dateideskriptor ersetzt, der mit tmpfile und verknüpft ist Danach wird die Verknüpfung der Datei sofort mit aufgehobenrm. Auf diese Weise kann die Funktion den Stream zuverlässig verarbeiten, ohne dass Traps erforderlich sind oder anderweitig bereinigt werden müssen. Sobald die Funktion den Anspruch auf dem fd freigibt, ist die tmpfile nicht mehr vorhanden, da der einzige benannte Dateisystemlink bereits entfernt wurde.

mikeserv
quelle
0

Verwenden Sie eine Bash-Funktion wie folgt:

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

Dies ist in diesem Fall ein bisschen übertrieben, aber wenn Ihre Filter größer werden, kann dies zu einem Segen werden.

mkalkov
quelle