Wie kann ich eine Textdatei in mehrere Textdateien aufteilen?

16

Ich habe eine Textdatei mit dem Namen entry.txt, die Folgendes enthält:

[ entry1 ]
1239 1240 1242 1391 1392 1394 1486 1487 1489 1600
1601 1603 1657 1658 1660 2075 2076 2078 2322 2323
2325 2740 2741 2743 3082 3083 3085 3291 3292 3294
3481 3482 3484 3633 3634 3636 3690 3691 3693 3766
3767 3769 4526 4527 4529 4583 4584 4586 4773 4774
4776 5153 5154 5156 5628 5629 5631
[ entry2 ]
1239 1240 1242 1391 1392 1394 1486 1487 1489 1600
1601 1603 1657 1658 1660 2075 2076 2078 2322 2323
2325 2740 2741 2743 3082 3083 3085 3291 3292 3294
3481 3482 3484 3690 3691 3693 3766 3767 3769 4526
4527 4529 4583 4584 4586 4773 4774 4776 5153 5154
5156 5628 5629 5631
[ entry3 ]
1239 1240 1242 1391 1392 1394 1486 1487 1489 1600
1601 1603 1657 1658 1660 2075 2076 2078 2322 2323
2325 2740 2741 2743 3082 3083 3085 3291 3292 3294
3481 3482 3484 3690 3691 3693 3766 3767 3769 4241
4242 4244 4526 4527 4529 4583 4584 4586 4773 4774
4776 5153 5154 5156 5495 5496 5498 5628 5629 5631

Ich möchte es in drei Textdateien aufgeteilt: entry1.txt, entry2.txt, entry3.txt. Ihre Inhalte sind wie folgt.

entry1.txt :

[ entry1 ]
1239 1240 1242 1391 1392 1394 1486 1487 1489 1600
1601 1603 1657 1658 1660 2075 2076 2078 2322 2323
2325 2740 2741 2743 3082 3083 3085 3291 3292 3294
3481 3482 3484 3633 3634 3636 3690 3691 3693 3766
3767 3769 4526 4527 4529 4583 4584 4586 4773 4774
4776 5153 5154 5156 5628 5629 5631

entry2.txt :

[ entry2 ]
1239 1240 1242 1391 1392 1394 1486 1487 1489 1600
1601 1603 1657 1658 1660 2075 2076 2078 2322 2323
2325 2740 2741 2743 3082 3083 3085 3291 3292 3294
3481 3482 3484 3690 3691 3693 3766 3767 3769 4526
4527 4529 4583 4584 4586 4773 4774 4776 5153 5154
5156 5628 5629 5631

entry3.txt :

[ entry3 ]
1239 1240 1242 1391 1392 1394 1486 1487 1489 1600
1601 1603 1657 1658 1660 2075 2076 2078 2322 2323
2325 2740 2741 2743 3082 3083 3085 3291 3292 3294
3481 3482 3484 3690 3691 3693 3766 3767 3769 4241
4242 4244 4526 4527 4529 4583 4584 4586 4773 4774
4776 5153 5154 5156 5495 5496 5498 5628 5629 5631

Mit anderen Worten, das [Zeichen gibt an, dass eine neue Datei beginnen soll. Die Einträge ( [ entry*]wobei *eine ganze Zahl ist) sind immer in numerischer Reihenfolge und sind aufeinanderfolgende ganze Zahlen von 1 bis N (in meiner tatsächlichen Eingabedatei N = 200001).

Gibt es eine Möglichkeit, die automatische Aufteilung von Textdateien in Bash durchzuführen? Meine eigentliche Eingabe entry.txtenthält tatsächlich 200.001 Einträge.

Andrew
quelle

Antworten:

11

Und hier ist ein netter, einfacher Einzeiler:

$ gawk '/^\[/{match($0, /^\[ (.+?) \]/, k)} {print >k[1]".txt" }' entry.txt

Dies funktioniert für jede Dateigröße, unabhängig von der Anzahl der Zeilen in jedem Eintrag, solange jeder Eintragskopf aussieht [ blahblah blah blah ]. Beachten Sie den Raum kurz nach dem Öffnen [und kurz vor dem Schließen ].


ERLÄUTERUNG:

awkund gawklesen Sie eine Eingabedatei Zeile für Zeile. Während des Lesens jeder Zeile wird deren Inhalt in der $0Variablen gespeichert . Hier sagen wir, dass gawkalles in eckigen Klammern übereinstimmen und die Übereinstimmung im Array speichern soll k.

Jedes Mal, wenn dieser reguläre Ausdruck abgeglichen wird, dh für jeden Header in Ihrer Datei, hat k [1] den abgeglichenen Bereich der Zeile. Nämlich "Eintrag1", "Eintrag2" oder "Eintrag3" oder "EintragN".

Schließlich drucken wir jede Zeile in eine Datei mit dem Namen <whatever value k currently has>.txtentry1.txt, entry2.txt ... entryN.txt.

Diese Methode ist für größere Dateien viel schneller als Perl.

terdon
quelle
+1 schön. Sie müssen nicht zum matchEintrag: /^\[/ { name=$2 }sollte ausreichen.
Thor
Vielen Dank @Thor. Ihr Vorschlag ist für den beschriebenen Fall korrekt, setzt jedoch voraus, dass der Name des Eintrags niemals ein Leerzeichen enthält. Deshalb habe ich das Beispiel [ blahblah blah blah ]in meiner Antwort verwendet.
Terdon
Ah, ich habe das bisschen über durch Leerzeichen getrennte Einträge verpasst. Sie könnten auch solche mit aufnehmen FS, z -F '\\[ | \\]'.
Thor
@terdon Ich mag diese kurzen Lösungen sehr, leider kann ich sie normalerweise nicht auf meine Bedürfnisse verallgemeinern. Könnten Sie mir helfen? Meine Datei enthält Zeilen, die mit beginnen #S x, wobei x eine 1-, 2- oder 3-stellige Zahl ist. Das Speichern in x.dat würde ausreichen. Ich habe versucht: gawk '/^#S/{match($0, / [0-9]* /, k)} {print >k[1]".dat" }' myFile.txtund einige Variationen davon.
Mikuszefski
Habe es geschafft gawk '/^#S/{match($0, /^#S (\s+?)([0-9]+)(\s+?)/, k)} {print >k[2]".txt" }' test.txt. Verstehe die Array-Nummer allerdings nicht 2sehr gut.
Mikuszefski
17

Mit csplit von GNU coreutils (nicht eingebettetes Linux, Cygwin):

csplit -f entry -b '%d.txt' entry.txt '/^\[ .* \]$/' '{*}'

Am Ende erhalten Sie eine zusätzliche leere Datei entry0.txt(die den Teil vor dem ersten Header enthält).

In Standard- csplit fehlen der {*}unbestimmte Repeater und die -bOption, das Suffix-Format anzugeben. In anderen Systemen müssen Sie also zuerst die Anzahl der Abschnitte zählen und anschließend die Ausgabedateien umbenennen.

csplit -f entry -n 9 entry.txt '/^\[ .* \]$/' "{$(egrep -c '^'\[ .* \]$' <entry.txt)}"
for x in entry?????????; do
  y=$((1$x - 1000000000))
  mv "entry$x" "entry$y.txt"
done
Gilles 'SO - hör auf böse zu sein'
quelle
Ich finde, dass csplit manchmal etwas schrullig ist, aber unglaublich nützlich, wenn ich so etwas machen möchte.
ixtmixilix
10

In Perl kann es viel einfacher gemacht werden:

perl -ne 'open(F, ">", ($1).".txt") if /\[ (entry\d+) \]/; print F;' file
eilen
quelle
9

Hier ist ein kurzer Awk-Einzeiler:

awk '/^\[/ {ofn=$2 ".txt"} ofn {print > ofn}' input.txt

Wie funktioniert das?

  • /^\[/ Stimmt mit Zeilen überein, die mit einer eckigen Klammer links beginnen, und
  • {ofn=$2 ".txt"}Setzt eine Variable auf das zweite Wort mit weißem Abstand als unseren Ausgabedateinamen. Dann,
  • ofn ist eine Bedingung, die als wahr ausgewertet wird, wenn die Variable gesetzt ist (wodurch Zeilen vor dem ersten Header ignoriert werden).
  • {print > ofn} Leitet die aktuelle Zeile in die angegebene Datei um.

Beachten Sie, dass alle Leerzeichen in diesem awk-Skript entfernt werden können, wenn die Kompaktheit Sie glücklich macht.

Beachten Sie auch, dass das obige Skript wirklich die Abschnittsüberschriften benötigt, um Leerzeichen zu haben und nicht in ihnen. Wenn Sie in der Lage sein möchten, mit Abschnittsüberschriften wie [foo]und [ this that ]umzugehen, benötigen Sie so etwas mehr Code:

awk '/^\[/ {sub(/^\[ */,""); sub(/ *\] *$/,""); ofn=$0 ".txt"} ofn {print > ofn}' input.txt

Hierbei wird die awk- sub()Funktion verwendet, um führende und nachfolgende eckige Klammern plus Leerzeichen zu entfernen. Beachten Sie, dass durch das standardmäßige awk-Verhalten Leerzeichen (das Feldtrennzeichen) in ein einzelnes Leerzeichen reduziert werden (dh [ this that ]in gespeichert werden "this that.txt"). Wenn es wichtig ist, das ursprüngliche Leerzeichen in den Ausgabedateinamen beizubehalten, können Sie durch Festlegen von FS experimentieren.

ghoti
quelle
2

Dies kann über die Befehlszeile in Python erfolgen:

paddy$ python3 -c 'out=0
> with open("entry.txt") as f: 
>   for line in f:
>     if line[0] == "[":
>       if out: out.close()
>       out = open(line.split()[1] + ".txt", "w")
>     else: out.write(line)'
Paddy3118
quelle
2

Dies ist eine etwas grobe, aber leicht verständliche Methode: Verwenden Sie grep -l '[ entry ]' FILENAME, um die Zeilennummern bei [entry] aufzuteilen. Verwenden Sie eine Kombination aus Kopf und Schwanz, um die richtigen Teile zu erhalten.

Wie ich sagte; es ist nicht schön, aber leicht zu verstehen.

Sigurt Dinesen
quelle
2

Was ist mit awk [als Datensatztrennzeichen und Leerzeichen als Feldtrennzeichen ? Dies gibt uns leicht die Daten, die in der Datei abgelegt werden sollen, $0wo er den entfernten Anfangsbuchstaben [und den Dateinamen als zurücklegen muss $1. Wir müssen dann nur den Sonderfall des 1. Datensatzes behandeln, der leer ist. Das gibt uns:

awk -v "RS=[" -F " " 'NF != 0 {print "[" $0 > $1}' entry.txt
jfg956
quelle
2

Terdons Antwort funktioniert für mich, aber ich musste Gawk benutzen, nicht awk. Das Handbuch zu gawk (suche nach 'match (')) erklärt, dass das Array-Argument in match () eine gawk-Erweiterung ist. Vielleicht hängt es von deiner Linux-Installation und deinen awk / nawk / gawk-Versionen ab, aber auf meinem Ubuntu-Rechner lief nur gawk terdon's excellent Antworten:

$ gawk '{if(match($0, /^\[ (.+?) \]/, k)){name=k[1]}} {print >name".txt" }' entry.txt
user31371
quelle
1

Hier ist eine Perl-Lösung. Dieses Skript erkennt die [ entryN ]Zeilen und ändert die Ausgabedatei entsprechend, überprüft, analysiert oder verarbeitet jedoch nicht die Daten in den einzelnen Abschnitten, sondern druckt nur die Eingabezeile in die Ausgabedatei.

#! /usr/bin/perl 

# default output file is /dev/null - i.e. dump any input before
# the first [ entryN ] line.

$outfile='/dev/null';
open(OUTFILE,">",$outfile) || die "couldn't open $outfile: $!";

while(<>) {
  # uncomment next two lines to optionally remove comments (starting with
  # '#') and skip blank lines.  Also removes leading and trailing
  # whitespace from each line.
  # s/#.*|^\s*|\s*$//g;
  # next if (/^$/)

  # if line begins with '[', extract the filename
  if (m/^\[/) {
    (undef,$outfile,undef) = split ;
    close(OUTFILE);
    open(OUTFILE,">","$outfile.txt") || die "couldn't open $outfile.txt: $!";
  } else {
    print OUTFILE;
  }
}
close(OUTFILE);
cas
quelle
1

Hallo, ich habe dieses einfache Skript mit Ruby geschrieben, um Ihr Problem zu lösen

#!ruby
# File Name: split.rb

fout = nil

while STDIN.gets
  line = $_
  if line.start_with? '['
    fout.close if fout
    fname = line.split(' ')[1] + '.txt'
    fout = File.new fname,'w'
  end
  fout.write line if fout
end

fout.close if fout

du kannst es so benutzen:

ruby split.rb < entry.txt

Ich habe es getestet, und es funktioniert gut ..

Kokizzu
quelle
1

Ich bevorzuge die csplitOption, aber als Alternative gibt es hier eine GNU awk-Lösung:

parse.awk

BEGIN { 
  RS="\\[ entry[0-9]+ \\]\n"  # Record separator
  ORS=""                      # Reduce whitespace on output
}
NR == 1 { f=RT }              # Entries are of-by-one relative to matched RS
NR  > 1 {
  split(f, a, " ")            # Assuming entries do not have spaces 
  print f  > a[2] ".txt"      # a[2] now holds the bare entry name
  print   >> a[2] ".txt"
  f = RT                      # Remember next entry name
}

Führen Sie es so aus:

gawk -f parse.awk entry.txt
Thor
quelle
1
FWIW RTscheint die Variable gawk-spezifisch zu sein. Diese Lösung funktioniert mit FreeBSD awk nicht.
Ghoti
@ghoti: Richtig, das hätte ich erwähnen sollen. Ich habe das jetzt in die Antwort aufgenommen. Vielen Dank.
Thor