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 XYZ
so aufteilen, dass sie file1
die Zeilen bis zu XYZ
und den Rest der Zeilen enthält file2
.
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 XYZ
so aufteilen, dass sie file1
die Zeilen bis zu XYZ
und den Rest der Zeilen enthält file2
.
Antworten:
Mit awk
können Sie:
awk '{print >out}; /XYZ/{out="file2"}' out=file1 largefile
Erläuterung: Das erste awk
Argument ( out=file1
) definiert eine Variable mit dem Dateinamen, die für die Ausgabe verwendet wird, während das nachfolgende Argument ( largefile
) verarbeitet wird. Das awk
Programm druckt alle Zeilen in die durch die Variable out
( {print >out}
) angegebene Datei . Wenn das Muster gefunden XYZ
wird, 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:
Dies ist ein Job für csplit
:
csplit -sf file -n 1 large_file /XYZ/
würde s
ilently die Akte aufteilen und Stücke mit pre verursachenf
ix file
und n
umbered eine einzelne Ziffer, zB unter Verwendung file0
usw. 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, file0
und file1
. Wenn Sie unbedingt einen Namen benötigen file1
und file2
dem csplit
Befehl immer ein leeres Muster hinzufügen und die erste Datei entfernen möchten:
csplit -sf file -n 1 large_file // /XYZ/+1
schafft file0
, file1
und file2
doch file0
ist leer , so dass Sie es sicher entfernen können:
rm -f file0
Mit einem modernen ksh
hier ist eine Shell-Variante (dh ohne sed
) einer der sed
oben 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 ksh
Lö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).
read
und print
- Sie sollten es einfach gehen lassen, um alles selbst auszugeben. Die Leistung wird besser, wenn Sie das AST-Toolkit vollständig ksh
erstellen und alle eingebauten Komponenten kompilieren - für mich sed
ist es seltsam, dass dies eigentlich keine davon ist. Aber bei while <file do
sed
awk
Ihre Benchmark entwickelt? Und obwohl ich mir ziemlich sicher bin ksh
, dass Sie diesen Kampf wahrscheinlich immer gewinnen werden, sind Sie mit GNU sed
nicht sehr fair umzugehen sed
- GNUs -u
nbuffered 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.
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 while
Schleife ist vermutlich wesentlich langsamer (wurde jedoch nicht überprüft).
head
anstelle der read
; es scheint nur ein wenig langsamer, aber es ist knapperer Code: { head -1 <##XYZ ; { read <##"" ;} >file4 ;} <largefile >file3
.
Versuchen Sie dies mit GNU sed:
sed -n -e '1,/XYZ/w file1' -e '/XYZ/,${/XYZ/d;w file2' -e '}' large_file
sed -e '1,/XYZ/{w file1' -e 'd}' large_file > file2
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 f
und die beiden Ausgabedateien f1
und f2
:
Verwerfen der Linie, die dem Aufteilungsmuster entspricht:
perl -ne 'if(/XYZ/){$a=1; next} ; $a==1 ? print STDERR : print STDOUT;' f >f1 2>f2
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:
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
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
XYZ
Zeile in die Ausgabe einbezogen werden oder nicht?