Datei in zwei Teile teilen, nach einem Muster


14

Wie teilt man eine große Datei in zwei Teile, nach einem Muster?

Ein Beispiel gegeben file.txt:

ABC
EFG
XYZ
HIJ
KNL

Ich möchte diese Datei XYZso aufteilen, dass sie file1die Zeilen bis zu XYZund den Rest der Zeilen enthält file2.


Soll die XYZZeile in die Ausgabe einbezogen werden oder nicht?
terdon

@terdon In meinem Fall sollte keine "XYZ" -Zeile nicht Teil von file2 sein. Wenn Sie jedoch eine Möglichkeit dazu haben, fügen Sie diese bitte zur Antwort hinzu. In einigen anderen Fällen kann dies hilfreich sein.
Dienstag,

Fair genug, fertig.
terdon

Antworten:


10

Mit awkkönnen Sie:

awk '{print >out}; /XYZ/{out="file2"}' out=file1 largefile


Erläuterung: Das erste awkArgument ( out=file1) definiert eine Variable mit dem Dateinamen, die für die Ausgabe verwendet wird, während das nachfolgende Argument ( largefile) verarbeitet wird. Das awkProgramm druckt alle Zeilen in die durch die Variable out( {print >out}) angegebene Datei . Wenn das Muster gefunden XYZwird, wird die Ausgabevariable neu definiert, um auf die neue Datei ( {out="file2}") zu verweisen, die als Ziel zum Drucken der nachfolgenden Datenzeilen verwendet wird.

Verweise:


14

Dies ist ein Job für csplit:

csplit -sf file -n 1 large_file /XYZ/

würde silently die Akte aufteilen und Stücke mit pre verursachenf ix fileund numbered eine einzelne Ziffer, zB unter Verwendung file0usw. Beachten Sie, dass mit /regex/, würde sich trennen , aber nicht einschließlich der Linie , dass Streichhölzer regex. Fügen Sie einen Versatz hinzu, um die Zeilenübereinstimmung aufzuteilen und einzuschließen:regex+1

csplit -sf file -n 1 large_file /XYZ/+1

Dadurch entstehen zwei Dateien, file0und file1. Wenn Sie unbedingt einen Namen benötigen file1und file2dem csplitBefehl immer ein leeres Muster hinzufügen und die erste Datei entfernen möchten:

csplit -sf file -n 1 large_file // /XYZ/+1

schafft file0 , file1und file2doch file0ist leer , so dass Sie es sicher entfernen können:

rm -f file0

Ich denke, das ist die einfachste Antwort. Alles, was Sie tun müssen, ist, einige Muster aufzulisten, und die Datei wird in dieser Reihenfolge aufgeteilt. Brillant!
Henry Blyth

6

Mit einem modernen ksh hier ist eine Shell-Variante (dh ohne sed) einer der sedoben genannten Antworten:

{ read in <##XYZ ; print "$in" ; cat >file2 ;} <largefile >file1


Und noch eine Variante in ksh alleine (also auch weglassen cat):

{ read in <##XYZ ; print "$in" ; { read <##"" ;} >file2 ;} <largefile >file1


(Die reine kshLösung scheint ziemlich performant zu sein; bei einer 2,4-GB-Testdatei dauerte es 19-21 Sekunden, verglichen mit 39-47 Sekunden bei der sed/cat -basierten Ansatz).


Das geht sehr schnell. Aber ich denke nicht, dass Sie müssen readund print- Sie sollten es einfach gehen lassen, um alles selbst auszugeben. Die Leistung wird besser, wenn Sie das AST-Toolkit vollständig ksherstellen und alle eingebauten Komponenten kompilieren - für mich sedist es seltsam, dass dies eigentlich keine davon ist. Aber bei while <file dosed
solchen

Ich bin allerdings gespannt - wie hat sich awkIhre Benchmark entwickelt? Und obwohl ich mir ziemlich sicher bin ksh, dass Sie diesen Kampf wahrscheinlich immer gewinnen werden, sind Sie mit GNU sednicht sehr fair umzugehen sed- GNUs -unbuffered ist ein pissarmer Ansatz für POSIXLY, um sicherzustellen, dass der Offset des Deskriptors dort verbleibt, wo das Programm beendet wird Es sollte keine Notwendigkeit bestehen, den regulären Betrieb des Programms zu verlangsamen. Die Pufferung ist in Ordnung. Alles sed, was Sie tun müssen, ist, den Deskriptor zu suchen, wenn Sie fertig sind. Aus irgendeinem Grund kehrt GNU diese Mentalität um.
mikeserv

@mikeserv; Die Umleitungsmusterübereinstimmung wird durchgeführt, bis das Muster gefunden wurde, und die Zeile mit dem gefundenen Muster wird nicht gedruckt, wenn dies nicht explizit wie abgebildet erfolgt. (Zumindest hat das meinen Test gezeigt.) Beachten Sie, dass es keinen gibt while; Das Drucken erfolgt implizit als definierter Nebeneffekt des <##Umleitungsoperators. Und nur die passende Zeile muss gedruckt werden. (Auf diese Weise ist die Implementierung des Shell-Features für die Unterstützung von incl./excl am flexibelsten.) Eine explizite whileSchleife ist vermutlich wesentlich langsamer (wurde jedoch nicht überprüft).
Janis

1
@mikeserv; Ah, okay. Übrigens habe ich gerade versucht, die headanstelle der read; es scheint nur ein wenig langsamer, aber es ist knapperer Code: { head -1 <##XYZ ; { read <##"" ;} >file4 ;} <largefile >file3.
Janis

1
@mikeserv; Guter Punkt; es war nicht. Aber wenn ich das eingebaute aktiviere (gerade fertig und die Ergebnisse überprüft), sind es seltsamerweise die gleichen Zahlen. (Vielleicht etwas Funktionsaufruf-Overhead im Vergleich zu lesen?)
Janis

6
{ sed '/XYZ/q' >file1; cat >file2; } <infile

Bei GNU sedsollten Sie den -uSchalter nbuffered verwenden. Die meisten anderen seds sollten jedoch funktionieren.

XYZ weglassen ...

{ sed -n '/XYZ/q;p'; cat >file2; } <infile >file1

3

Versuchen Sie dies mit GNU sed:

sed -n -e '1,/XYZ/w file1' -e '/XYZ/,${/XYZ/d;w file2' -e '}' large_file

Kürzere:sed -e '1,/XYZ/{w file1' -e 'd}' large_file > file2
don_crissti

1

Ein einfacher Hack besteht darin, entweder auf STDOUT oder STDERR zu drucken, je nachdem, ob das Zielmuster übereinstimmt. Sie können dann die Umleitungsoperatoren der Shell verwenden , um die Ausgabe entsprechend umzuleiten. Beispiel: In Perl wird angenommen, dass die Eingabedatei aufgerufen wird fund die beiden Ausgabedateien f1und f2:

  1. Verwerfen der Linie, die dem Aufteilungsmuster entspricht:

    perl -ne 'if(/XYZ/){$a=1; next} ; $a==1 ? print STDERR : print STDOUT;' f >f1 2>f2
  2. Einschließlich der übereinstimmenden Zeile:

    perl -ne '$a=1 if /XYZ/; $a==1 ? print STDERR : print STDOUT;' f >f1 2>f2

Alternativ können Sie auch in andere Dateizugriffsnummern drucken:

  1. Verwerfen der Linie, die dem Aufteilungsmuster entspricht:

    perl -ne 'BEGIN{open($fh1,">","f1");open($fh2,">","f2");}
    if(/XYZ/){$a=1; next}$a==1 ? print $fh1 "$_" : print $fh2 "$_";' f
  2. Einschließlich der übereinstimmenden Zeile:

    perl -ne 'BEGIN{open($fh1,">","f1"); open($fh2,">","f2");}
              $a=1 if /XYZ/; $a==1 ? print $fh1 "$_" : print $fh2 "$_";' f
Durch die Nutzung unserer Website bestätigen Sie, dass Sie unsere Cookie-Richtlinie und Datenschutzrichtlinie gelesen und verstanden haben.
Licensed under cc by-sa 3.0 with attribution required.