Wie kann ich unter Unix einen vorgegebenen Zeilenbereich aus einer Textdatei extrahieren?

531

Ich habe einen SQL-Speicherauszug mit ~ 23000 Zeilen, der Daten im Wert von mehreren Datenbanken enthält. Ich muss einen bestimmten Abschnitt dieser Datei (dh die Daten für eine einzelne Datenbank) extrahieren und in eine neue Datei einfügen. Ich kenne sowohl die Start- als auch die Endzeilennummer der gewünschten Daten.

Kennt jemand einen Unix-Befehl (oder eine Reihe von Befehlen), um alle Zeilen aus einer Datei zwischen den Zeilen 16224 und 16482 zu extrahieren und sie dann in eine neue Datei umzuleiten?

Adam J. Forster
quelle
Da Sie große Dateien erwähnen, empfehle ich, den Kommentar stackoverflow.com/questions/83329/…
sancho.s ReinstateMonicaCellio

Antworten:

792
sed -n '16224,16482p;16483q' filename > newfile

Aus dem sed-Handbuch :

p - Drucken Sie den Musterbereich aus (zur Standardausgabe). Dieser Befehl wird normalerweise nur in Verbindung mit der Befehlszeilenoption -n verwendet.

n - Wenn das automatische Drucken nicht deaktiviert ist, drucken Sie den Musterbereich und ersetzen Sie den Musterbereich unabhängig davon durch die nächste Eingabezeile. Wenn keine Eingabe mehr erfolgt, wird sed beendet, ohne dass weitere Befehle verarbeitet werden.

q - Beenden, sedohne weitere Befehle oder Eingaben zu verarbeiten. Beachten Sie, dass der aktuelle Musterbereich gedruckt wird, wenn der automatische Druck mit der Option -n nicht deaktiviert ist.

und

Adressen in einem sed-Skript können eine der folgenden Formen haben:

number Die Angabe einer Zeilennummer stimmt nur mit dieser Zeile in der Eingabe überein.

Ein Adressbereich kann angegeben werden, indem zwei durch Komma (,) getrennte Adressen angegeben werden. Ein Adressbereich stimmt mit Zeilen überein, die an der Stelle beginnen, an der die erste Adresse übereinstimmt, und wird fortgesetzt, bis die zweite Adresse (einschließlich) übereinstimmt.

boxxar
quelle
3
Ich war neugierig, ob dies die Originaldatei ändert. Ich habe es nur für den Fall gesichert und es scheint, dass dies das Original NICHT wie erwartet verändert hat.
Andy Groff
@ AndyGroff. Verwenden Sie den Parameter "-i", um die vorhandene Datei zu ändern. Andernfalls wird die Datei nicht geändert.
Youri
175
Wenn Sie dies wie ich für eine SEHR große Datei tun müssen, ist es hilfreich, wenn Sie in der nächsten Zeile einen Beendigungsbefehl hinzufügen. Dann ist es sed -n '16224,16482p;16483q' filename. Andernfalls wird sed bis zum Ende weiter scannen (oder zumindest meine Version).
WDS
7
@ MilesRout Leute scheinen zu fragen "Warum die Abwertung?" ziemlich oft meinst du vielleicht "Es ist mir egal" statt "Niemand kümmert sich darum"
Mark
1
@wds - Ihr Kommentar verdient eine Antwort, die nach oben klettert. Es kann den Unterschied zwischen Tag und Nacht machen.
sancho.s ReinstateMonicaCellio
203
sed -n '16224,16482 p' orig-data-file > new-file

Wobei 16224,16482 die Startzeilennummer und die Endzeilennummer einschließlich sind. Dies ist 1-indiziert. -nUnterdrückt das Echo der Eingabe als Ausgabe, was Sie eindeutig nicht möchten. Die Zahlen geben den Zeilenbereich an, mit dem der folgende Befehl ausgeführt werden soll. Der Befehl pdruckt die entsprechenden Zeilen aus.

JXG
quelle
7
Bei großen Dateien geht der obige Befehl die gesamte Datei weiter, nachdem der gewünschte Bereich gefunden wurde. Gibt es eine Möglichkeit, die Verarbeitung der Datei zu beenden, sobald der Bereich ausgegeben wurde?
Gary
39
Nun, aus der Antwort hier geht hervor , dass ein Stopp am Ende des Bereichs erreicht werden könnte mit : sed -n '16224,16482p;16482q' orig-data-file > new-file.
Gary
5
Warum sollten Sie einen unnötigen Platz einfügen und dann zitieren müssen? (Natürlich ist es die Essenz der Hälfte der Informatik, unnötige Probleme zu machen und sie zu lösen, aber ich meine neben diesem Grund ...)
Kaz
92

Ganz einfach mit Kopf / Schwanz:

head -16482 in.sql | tail -258 > out.sql

mit sed:

sed -n '16482,16482p' in.sql > out.sql

mit awk:

awk 'NR>=10&&NR<=20' in.sql > out.sql
Manveru
quelle
1
Die zweite und dritte Option sind in Ordnung, aber die erste ist langsamer als viele Alternativen, da 2 Befehle verwendet werden, wobei 1 ausreicht. Es erfordert auch eine Berechnung, um das richtige Argument zu erhalten tail.
Jonathan Leffler
3
Es ist erwähnenswert, dass, um die gleichen Zeilennummern wie die Frage sed -n 16224,16482p' in.sql >out.sqlawk 'NR>=16224&&NR<=16482' in.sql > out.sql
beizubehalten
3
Es ist auch erwähnenswert, dass im Fall des ersten Beispiels head -16482 in.sql | tail -$((16482-16224)) >out.sqldie Berechnung auf bash
sibaz
1
Die erste mit Kopf und Schwanz WAYYYY schneller bei großen Dateien als die sed-Version, auch wenn die q-Option hinzugefügt wurde. Kopf-Version Instant und Sed-Version I Strg-C nach einer Minute ... Danke
Miyagi
2
Könnte auch verwendet werden tail -n +16224, um die Berechnung zu reduzieren
SOFe
35

Sie könnten 'vi' und dann den folgenden Befehl verwenden:

:16224,16482w!/tmp/some-file

Alternative:

cat file | head -n 16482 | tail -n 258

BEARBEITEN: - Nur um eine Erklärung hinzuzufügen, verwenden Sie head -n 16482 , um die ersten 16482 Zeilen anzuzeigen, und verwenden Sie tail -n 258 , um die letzten 258 Zeilen aus der ersten Ausgabe herauszuholen.

Mark Janssen
quelle
2
Und anstelle von vi könnten Sie ex verwenden, das ist vi minus interaktives Konsolenmaterial.
Tadeusz A. Kadłubowski
1
Sie brauchen den catBefehl nicht; headkann eine Datei direkt lesen. Dies ist langsamer als bei vielen Alternativen, da 2 (3 wie gezeigt) Befehle verwendet werden, wobei 1 ausreicht.
Jonathan Leffler
1
@ JonathanLeffler Du liegst ganz falsch. Es ist unglaublich schnell. Ich extrahiere 200k Zeilen, ungefähr 1G, aus einer 2G-Datei mit 500k Zeilen in wenigen Sekunden (ohne die cat). Andere Lösungen benötigen mindestens einige Minuten. Auch die schnellste Variante von GNU scheint zu sein tail -n +XXX filename | head XXX.
Antonis Christofides
28

Es gibt einen anderen Ansatz mit awk:

awk 'NR==16224, NR==16482' file

Wenn die Datei sehr groß ist, kann es gut sein, exitnach dem Lesen der letzten gewünschten Zeile. Auf diese Weise werden die folgenden Zeilen nicht unnötig gelesen:

awk 'NR==16224, NR==16482-1; NR==16482 {print; exit}' file

awk 'NR==16224, NR==16482; NR==16482 {exit}' file
fedorqui 'SO hör auf zu schaden'
quelle
2
1+ zum Einsparen von Laufzeit und Ressourcen mithilfe von print; exit. Vielen Dank !
Bernie Reiter
Leichte Vereinfachung des 2. Beispiels:awk 'NR==16224, NR==16482; NR==16482 {exit}' file
Robin A. Meade
Das ist hell, danke @ RobinA.Meade! Ich bearbeitete Ihre Idee in der Post
fedorqui ‚SO Anschlag zu schaden‘
17
perl -ne 'print if 16224..16482' file.txt > new_file.txt
mmaibaum
quelle
9
 # print section of file based on line numbers
 sed -n '16224 ,16482p'               # method 1
 sed '16224,16482!d'                 # method 2
Cetra
quelle
6
cat dump.txt | head -16224 | tail -258

sollte den Trick machen. Der Nachteil dieses Ansatzes ist, dass Sie die Arithmetik ausführen müssen, um das Argument für tail zu bestimmen und zu berücksichtigen, ob das 'zwischen' die Endzeile enthalten soll oder nicht.

JP Lodine
quelle
4
Sie brauchen den catBefehl nicht; headkann eine Datei direkt lesen. Dies ist langsamer als bei vielen Alternativen, da 2 (3 wie gezeigt) Befehle verwendet werden, wobei 1 ausreicht.
Jonathan Leffler
@ JonathanLeffler Diese Antwort ist am einfachsten zu lesen und zu merken. Wenn Sie sich wirklich um Leistung gekümmert hätten, hätten Sie überhaupt keine Shell verwendet. Es ist empfehlenswert, bestimmte Tools einer bestimmten Aufgabe widmen zu lassen. Weiterhin kann die "Arithmetik" mit aufgelöst werden | tail -$((16482 - 16224)).
Yeti
6

Ich stehe auf den Schultern von Boxxar und mag Folgendes:

sed -n '<first line>,$p;<last line>q' input

z.B

sed -n '16224,$p;16482q' input

Das $bedeutet "letzte Zeile", also seddruckt der erste Befehl alle Zeilen beginnend mit der Zeile 16224und der zweite Befehl sedbeendet nach dem Drucken der Zeile 16428. (Das Hinzufügen 1des qBereichs in der Lösung von boxxar scheint nicht erforderlich zu sein.)

Ich mag diese Variante, weil ich die Endzeilennummer nicht zweimal angeben muss. Und ich habe gemessen, dass die Verwendung $keine nachteiligen Auswirkungen auf die Leistung hat.

Tilman Vogel
quelle
5

sed -n '16224,16482p' < dump.sql

cubex
quelle
3

Schnell und dreckig:

head -16428 < file.in | tail -259 > file.out

Wahrscheinlich nicht der beste Weg, aber es sollte funktionieren.

Übrigens: 259 = 16482-16224 + 1.

jan.vdbergh
quelle
Dies ist langsamer als bei vielen Alternativen, da 2 Befehle verwendet werden, wobei 1 ausreicht.
Jonathan Leffler
3

Ich habe ein Haskell-Programm namens Splitter geschrieben , das genau dies tut: Lesen Sie meinen Release-Blog-Beitrag durch .

Sie können das Programm wie folgt verwenden:

$ cat somefile | splitter 16224-16482

Und das ist alles, was dazu gehört. Sie benötigen Haskell, um es zu installieren. Gerade:

$ cabal install splitter

Und du bist fertig. Ich hoffe, dass Sie dieses Programm nützlich finden.

Robert Massaioli
quelle
Liest splitternur von der Standardeingabe? In gewissem Sinne spielt es keine Rolle; Der catBefehl ist überflüssig, ob er es tut oder nicht. Verwenden Sie entweder splitter 16224-16482 < somefileoder (wenn Dateinamenargumente erforderlich sind) splitter 16224-16482 somefile.
Jonathan Leffler
3

Sogar wir können dies tun, um in der Befehlszeile zu überprüfen:

cat filename|sed 'n1,n2!d' > abc.txt

Zum Beispiel:

cat foo.pl|sed '100,200!d' > abc.txt
Chinmoy Padhi
quelle
6
catIn beiden Fällen benötigen Sie den Befehl nicht. sedist perfekt in der Lage, Dateien selbst zu lesen, oder Sie können Standardeingaben aus einer Datei umleiten.
Jonathan Leffler
3

Mit Rubin:

ruby -ne 'puts "#{$.}: #{$_}" if $. >= 32613500 && $. <= 32614500' < GND.rdf > GND.extract.rdf
Carl Blakeley
quelle
2

Ich wollte gerade den Kopf / Schwanz-Trick posten, aber eigentlich würde ich wahrscheinlich nur Emacs starten. ;-);

  1. esc- gehe zur xLinie ret16224
  2. markiere ( ctrl- space)
  3. esc- gehe zur xLinie ret16482
  4. esc- -w

Öffnen Sie die neue Ausgabedatei und speichern Sie sie mit ctl-y

Lassen Sie mich sehen, was passiert.

sammyo
quelle
4
Emacs funktioniert meiner Erfahrung nach bei sehr großen Dateien nicht sehr gut.
Greg Mattes
Können Sie das als Skriptaktion ausführen oder ist es nur eine interaktive Option?
Jonathan Leffler
2

Ich würde ... benutzen:

awk 'FNR >= 16224 && FNR <= 16482' my_file > extracted.txt

FNR enthält die Datensatznummer der Zeile, die aus der Datei gelesen wird.

Paddy3118
quelle
2

Ich wollte dasselbe mit einem Skript unter Verwendung einer Variablen tun und erreichte dies, indem ich die $ -Variable in Anführungszeichen setzte, um den Variablennamen vom p zu trennen:

sed -n "$first","$count"p imagelist.txt >"$imageblock"

Ich wollte eine Liste in separate Ordner aufteilen und fand die erste Frage und beantwortete einen nützlichen Schritt. (Split-Befehl ist keine Option auf dem alten Betriebssystem, auf das ich den Code portieren muss).

KevinY
quelle
1

Ich habe ein kleines Bash-Skript geschrieben, das Sie über Ihre Befehlszeile ausführen können, solange Sie Ihren PATH so aktualisieren, dass er sein Verzeichnis enthält (oder Sie können ihn in einem Verzeichnis ablegen, das bereits im PATH enthalten ist).

Verwendung: $ pinch Dateiname Startzeile Endzeile

#!/bin/bash
# Display line number ranges of a file to the terminal.
# Usage: $ pinch filename start-line end-line
# By Evan J. Coon

FILENAME=$1
START=$2
END=$3

ERROR="[PINCH ERROR]"

# Check that the number of arguments is 3
if [ $# -lt 3 ]; then
    echo "$ERROR Need three arguments: Filename Start-line End-line"
    exit 1
fi

# Check that the file exists.
if [ ! -f "$FILENAME" ]; then
    echo -e "$ERROR File does not exist. \n\t$FILENAME"
    exit 1
fi

# Check that start-line is not greater than end-line
if [ "$START" -gt "$END" ]; then
    echo -e "$ERROR Start line is greater than End line."
    exit 1
fi

# Check that start-line is positive.
if [ "$START" -lt 0 ]; then
    echo -e "$ERROR Start line is less than 0."
    exit 1
fi

# Check that end-line is positive.
if [ "$END" -lt 0 ]; then
    echo -e "$ERROR End line is less than 0."
    exit 1
fi

NUMOFLINES=$(wc -l < "$FILENAME")

# Check that end-line is not greater than the number of lines in the file.
if [ "$END" -gt "$NUMOFLINES" ]; then
    echo -e "$ERROR End line is greater than number of lines in file."
    exit 1
fi

# The distance from the end of the file to end-line
ENDDIFF=$(( NUMOFLINES - END ))

# For larger files, this will run more quickly. If the distance from the
# end of the file to the end-line is less than the distance from the
# start of the file to the start-line, then start pinching from the
# bottom as opposed to the top.
if [ "$START" -lt "$ENDDIFF" ]; then
    < "$FILENAME" head -n $END | tail -n +$START
else
    < "$FILENAME" tail -n +$START | head -n $(( END-START+1 ))
fi

# Success
exit 0
DrNerdfighter
quelle
1
Dies ist langsamer als bei vielen Alternativen, da 2 Befehle verwendet werden, wobei 1 ausreicht. Tatsächlich liest es die Datei zweimal aufgrund des wcBefehls, der die Festplattenbandbreite verschwendet, insbesondere bei Gigabyte-Dateien. In vielerlei Hinsicht ist dies gut dokumentiert, aber es ist auch ein technischer Overkill.
Jonathan Leffler
1

Dies könnte für Sie funktionieren (GNU sed):

sed -ne '16224,16482w newfile' -e '16482q' file

oder Bash ausnutzen:

sed -n $'16224,16482w newfile\n16482q' file
Potong
quelle
1

Verwenden von ed:

ed -s infile <<<'16224,16482p'

-sunterdrückt die Diagnoseausgabe; Die eigentlichen Befehle befinden sich in einer Here-Zeichenfolge. Führt insbesondere 16224,16482pden pBefehl (Drucken) im gewünschten Zeilenadressbereich aus.

Benjamin W.
quelle
0

Das -n in den Antworten akzeptieren funktioniert. Hier ist ein anderer Weg, falls Sie dazu neigen.

cat $filename | sed "${linenum}p;d";

Dies bewirkt Folgendes:

  1. Pipe in den Inhalt einer Datei (oder geben Sie den Text ein, wie Sie möchten).
  2. sed wählt die angegebene Zeile aus und druckt sie aus
  3. d ist erforderlich, um Zeilen zu löschen, andernfalls geht sed davon aus, dass alle Zeilen eventuell gedruckt werden. Das heißt, ohne das d werden alle Zeilen, die von der ausgewählten Zeile gedruckt wurden, zweimal gedruckt, da der Teil $ {linenum} p Sie zum Drucken auffordert. Ich bin mir ziemlich sicher, dass das -n im Grunde das Gleiche tut wie das d hier.
ThinkBonobo
quelle
3
Notiz cat file | sedist besser geschrieben alssed file
fedorqui 'SO hör auf zu schaden'
Auch dies druckt nur eine Linie, während die Frage über eine Reihe von ihnen ist.
Fedorqui 'SO hör auf zu schaden'
0

Da es sich um das Extrahieren von Textzeilen aus einer Textdatei handelt, werde ich einen Sonderfall angeben, in dem Sie alle Zeilen extrahieren möchten, die einem bestimmten Muster entsprechen.

myfile content:
=====================
line1 not needed
line2 also discarded
[Data]
first data line
second data line
=====================
sed -n '/Data/,$p' myfile

Druckt die Zeile [Daten] und die verbleibenden Zeilen. Wenn Sie den Text von Zeile 1 bis zum Muster haben möchten, geben Sie Folgendes ein: sed -n '1, / Data / p' myfile. Wenn Sie zwei Muster kennen (besser in Ihrem Text eindeutig sein), können sowohl die Anfangs- als auch die Endzeile des Bereichs mit Übereinstimmungen angegeben werden.

sed -n '/BEGIN_MARK/,/END_MARK/p' myfile
Kemin Zhou
quelle