Sed Alternative zum Suchen und Ersetzen in sehr langen Zeilen

9

Ich habe Dateien, die von einem Programm generiert wurden, das keine Zeilenumbrüche am Ende von Datensätzen gesetzt hat. Ich möchte Zeilenumbrüche zwischen die Datensätze setzen, und ich kann dies mit einem einfachen sed-Skript tun:

sed -e 's/}{/}\n{/g'

Das Problem ist, dass die Eingabedateien mehrere Gigabyte groß sind und daher die zu sedierenden Eingabezeilen mehrere GB lang sind. sed versucht, eine Zeile im Speicher zu halten, was in diesem Fall nicht funktioniert. Ich habe die --unbufferedOption ausprobiert , aber das schien sie nur langsamer zu machen und erlaubte nicht, dass sie richtig beendet wurde.

Tom Panning
quelle
Wäre es möglich, irgendwo eine Beispiel-Eingabedatei hochzuladen, damit wir einige Ideen ausprobieren können?
mkc
3
Vielleicht könnten Sie zuerst verwenden trzu übersetzen }in \nund verwenden Sie dann sedein hinzuzufügen }jede Zeile am Ende? So:tr '}' '\n' < your_file.txt| sed 's/$/}/'
user43791
Hilft das Hinzufügen einer neuen Zeile am Ende der Datei überhaupt? Wie:printf "\n" >> file
Kindermädchen
1
@ Ketan, ich gehe davon aus, dass das Schreiben einer Datei mit 78 Garbage-Zeichen, gefolgt von einer }{Wiederholung, bis sie mehrere Gigabyte lang ist, ausreichen würde.
Kindermädchen
@nanny - guter Punkt - aber woher bekommst du 78? Wenn die Datensätze bereits blockiert sind, dd if=file cbs=80 conv=unblockwürde es das tun - aber es ist selten so einfach.
Mikeserv

Antworten:

7

Sie können ein anderes Tool verwenden, mit dem Sie das Trennzeichen für Eingabedatensätze festlegen können. Zum Beispiel

  • Perl

    perl -pe 'BEGIN{ $/="}{" } s/}{/}\n{/g' file
    

    Die spezielle Variable $/ist das Trennzeichen für Eingabedatensätze. Wenn Sie }{festlegen, dass Zeilen als endend in definiert werden }{. Auf diese Weise können Sie erreichen, was Sie wollen, ohne das Ganze in Erinnerung zu behalten.

  • mawk oder gawk

    awk -v RS="}{" -vORS= 'NR > 1 {print "}\n{"}; {print}' file 
    

    Das ist die gleiche Idee. Setzt RS="}{"das Datensatztrennzeichen auf }{und druckt dann }eine neue Zeile {(mit Ausnahme des ersten Datensatzes) und den aktuellen Datensatz.

terdon
quelle
3

Perl zur Rettung:

perl -i~ -e ' $/ = \1024;
              while (<>) {
                  print "\n" if $closing and /^{/;
                  undef $closing;
                  s/}{/}\n{/g;
                  print;
                  $closing = 1 if /}$/;
              } ' input1 input2

Bei Einstellung $/auf \1024wird die Datei in Blöcken von 1024 Byte gelesen. Die $closingVariable behandelt den Fall, wenn ein Block endet }und der nächste mit beginnt {.

Choroba
quelle
1
+1, wahrscheinlich die beste Lösung; Die anderen Perl / Awk-Lösungen funktionieren ebenfalls einwandfrei, aber was ist, wenn das erste Datensatztrennzeichen nach Zeichen im Wert von etwa 17 GB auftritt?
don_crissti
2

Du solltest tun:

{ <infile tr \} \\n;echo {; } | paste -d'}\n' - /dev/null >outfile

Es ist wahrscheinlich die effizienteste Lösung.

Dadurch {}werden mögliche nachfolgende Daten geschützt. Mit einem weiteren trVorgang können Sie dies vertauschen und am Anfang des ersten {Feldes eine Leerzeile einfügen. Mögen...

tr {} '}\n'| paste -d{\\0 /dev/null - | tr {}\\n \\n{}

Das erste mit Dons Beispieldaten lautet also:

printf '{one}{two}{three}{four}' |
{ tr \} \\n; echo {; }           |
paste -d'}\n' - /dev/null
{one}
{two}
{three}
{four}
{}

... und der zweite tut ...

printf '{one}{two}{three}{four}'      |
tr {} '}\n'| paste -d{\\0 /dev/null - |
tr {}\\n \\n{}
#leading blank
{one}
{two}
{three}
{four}

Für das zweite Beispiel gibt es keinen nachgestellten Zeilenumbruch - für das erste jedoch einen.

mikeserv
quelle