Ersetzen Sie die Zeichenfolge mit dem Zeilenumbruch in einer großen Datei

16

Kennt jemand ein nicht zeilenbasiertes Tool zum "binären" Suchen / Ersetzen von Zeichenfolgen auf etwas speichereffiziente Weise? Siehe auch diese Frage .

Ich habe eine + 2GB-Textdatei, die ich ähnlich wie diese verarbeiten möchte:

sed -e 's/>\n/>/g'

Das heißt, ich möchte alle Zeilenumbrüche entfernen, die nach einem auftreten >, aber nirgendwo anders, damit das ausschließt tr -d.

Dieser Befehl (den ich aus der Antwort auf eine ähnliche Frage erhalten habe ) schlägt fehl mit couldn't re-allocate memory:

sed --unbuffered ':a;N;$!ba;s/>\n/>/g'

Gibt es also andere Methoden, ohne auf C zurückzugreifen? Ich hasse Perl, bin aber bereit, in diesem Fall eine Ausnahme zu machen :-)

Ich weiß nicht genau, welches Zeichen in den Daten nicht vorkommt, daher \nmöchte ich es nach Möglichkeit vermeiden , es vorübergehend durch ein anderes Zeichen zu ersetzen .

Irgendwelche guten Ideen?

MattBianco
quelle
Haben Sie die Option ausprobiert --unbuffered?
Strg-Alt-Delor
Mit oder ohne --unbufferedSpeicherplatzmangel
MattBianco
Was macht $!das?
Strg-Alt-Delor
Was ist los mit dem ersten sed Befehl. Die zweite scheint zu sein, alles in den Musterraum zu lesen, ich weiß aber nicht, dass das so $!ist. Ich gehe davon aus, dass dies eine Menge Speicher benötigt.
Strg-Alt-Delor
Das Problem ist, dass sed alles als Zeilen liest, weshalb der erste Befehl die Zeilenumbrüche nicht entfernt, da er den Text erneut zeilenweise ausgibt. Der zweite Befehl ist nur eine Problemumgehung. Ich denke, sedist in diesem Fall nicht das richtige Werkzeug.
MattBianco

Antworten:

14

Das ist in Perl wirklich trivial, du solltest es nicht hassen!

perl -i.bak -pe 's/>\n/>/' file

Erläuterung

  • -i: Bearbeiten Sie die Datei an Ort und Stelle, und erstellen Sie eine Sicherungskopie des aufgerufenen Originals file.bak. Wenn Sie keine Sicherung wünschen, verwenden Sie perl -i -pestattdessen einfach .
  • -pe: Lies die Eingabedatei Zeile für Zeile und drucke jede Zeile nach dem Anwenden des Skripts aus -e.
  • s/>\n/>/: die Substitution, genau wie sed.

Und hier ist ein awkAnsatz:

awk  '{if(/>$/){printf "%s",$0}else{print}}' file2 
terdon
quelle
3
+1. Awk Golf:awk '{ORS=/>$/?"":"\n"}1'
Glenn Jackman
1
Warum ich Perl im Allgemeinen nicht mag, ist der gleiche Grund, warum ich diese Antwort (oder Ihren Kommentar zu Gnoucs Antwort) gewählt habe: Lesbarkeit. Die Verwendung von Perl -pe mit einem einfachen "sed-Muster" ist weitaus lesbarer als ein komplexer sed-Ausdruck.
MattBianco
3
@MattBianco fair genug, aber, nur damit Sie wissen, hat das nichts mit Perl zu tun. Das Aussehen, das Gnouc verwendet, ist ein Merkmal einiger regulärer Ausdruckssprachen (einschließlich, aber nicht beschränkt auf PCREs), nicht Perls Fehler. Nachdem Sie diese sed Monstrosität ':a;N;$!ba;s/>\n/>/g'in Ihrer Frage erwähnt haben, haben Sie auf Ihr Recht verzichtet, sich über die Lesbarkeit zu beschweren! : P
terdon
@glennjackman schön! Ich habe mit dem foo ? bar : bazKonstrukt gespielt, konnte es aber nicht zum Laufen bringen.
Terdon
@terdon: Ja, mein Fehler. Lösche es.
16.
7

Eine perlLösung:

$ perl -pe 's/(?<=>)\n//'

Erklärung

  • s/// wird für die Zeichenfolgensubstitution verwendet.
  • (?<=>) ist ein Lookbehind-Muster.
  • \n stimmt mit newline überein.

Das ganze Muster bedeutet, dass alle Zeilenumbrüche entfernt werden, die >davor stehen.

cuonglm
quelle
2
Möchtest du kommentieren, was die Teile des Programms bewirken? Ich bin immer auf der Suche zu lernen.
MattBianco
2
Warum sich um den Lookbehind kümmern? Warum nicht einfach s/>\n/>/?
Terdon
1
oder s/>\K\n//würde auch funktionieren
Glenn Jackman
@terdon: Nur das erste, was ich allerdings entferne, anstatt zu ersetzen
cuonglm
@glennjackman: guter Punkt!
Donnerstag,
3

Wie wäre es damit:

sed ':loop
  />$/ { N
    s/\n//
    b loop
  }' file

Bei GNU sed können Sie auch versuchen, die Option -u( --unbuffered) gemäß der Frage hinzuzufügen . GNU sed ist damit auch als einfacher Einzeiler zufrieden:

sed ':loop />$/ { N; s/\n//; b loop }' file
Graeme
quelle
Das entfernt nicht den letzten, \nwenn die Datei endet >\n, aber das ist wahrscheinlich sowieso vorzuziehen.
Stéphane Chazelas
@ StéphaneChazelas, warum muss das Closing }in einem separaten Ausdruck stehen? Funktioniert dies nicht als mehrzeiliger Ausdruck?
Graeme
1
Das funktioniert in POSIX-Seds mit b loop\n}oder -e 'b loop' -e '}'aber nicht als b loop;}und schon gar nicht als b loop}weil }und ;ist in Labelnamen gültig (obwohl niemand, der es richtig versteht, es verwenden würde. Und das bedeutet, dass GNU sed nicht POSIX-konform ist), und der }Befehl muss getrennt werden aus dem bBefehl.
Stéphane Chazelas
@ StéphaneChazelas, GNU sedist mit all dem zufrieden, auch mit --posix! Der Standard hat auch die folgenden Angaben für Klammerausdrücke - The list of sed functions shall be surrounded by braces and separated by <newline>s. Bedeutet dies nicht, dass Semikolons nur außerhalb von geschweiften Klammern verwendet werden sollten?
Graeme
@mikeserv, die Schleife wird benötigt, um aufeinanderfolgende Zeilen zu verarbeiten, die auf enden >. Das Original hatte nie eines, darauf wies Stéphane hin.
Graeme
1

Sie sollten in der Lage sein, sedden NBefehl zu verwenden, aber der Trick besteht darin, bei jedem Hinzufügen einer weiteren Zeile eine Zeile aus dem Musterbereich zu löschen (sodass der Musterbereich immer nur zwei aufeinanderfolgende Zeilen enthält, anstatt zu versuchen, das Ganze einzulesen Datei) - versuchen

sed ':a;$!N;s/>\n/>/;P;D;ba'

EDIT: nach dem erneuten Lesen von Peteris Krumins ' Famous Sed One-Liners Explained glaube ich, dass eine bessere sedLösung wäre

sed -e :a -e '/>$/N; s/\n//; ta'

Dies hängt die folgende Zeile nur an, wenn sie bereits >am Ende übereinstimmt, und sollte eine bedingte Schleife ausführen , um den Fall aufeinanderfolgender übereinstimmender Zeilen zu behandeln (dies ist Krumins 39. Fügen Sie eine Zeile an die nächste an, wenn sie mit einem Backslash endet "\" genau mit Ausnahme der Ersetzung von >for \als Verknüpfungszeichen und der Tatsache, dass das Verknüpfungszeichen in der Ausgabe beibehalten wird).

Stahlfahrer
quelle
2
Das funktioniert nicht, wenn 2 aufeinanderfolgende Zeilen auf enden >(das ist auch GNU-spezifisch)
Stéphane Chazelas
1

sedbietet keine Möglichkeit, eine Ausgabe ohne eine letzte neue Zeile auszugeben. Ihre Vorgehensweise mit der Verwendung von Nfunktioniert grundsätzlich, speichert jedoch unvollständige Zeilen im Speicher und kann daher fehlschlagen, wenn die Zeilen zu lang werden (sed-Implentationen sind normalerweise nicht für extrem lange Zeilen ausgelegt).

Sie können stattdessen awk verwenden.

awk '{if (/<$/) printf "%s", $0; else print}'

Ein alternativer Ansatz besteht darin tr, das Newline-Zeichen durch ein "langweiliges", häufig vorkommendes Zeichen zu ersetzen. Hier könnte Platz funktionieren - wählen Sie ein Zeichen, das in jeder Zeile oder zumindest in einem großen Teil der Zeilen in Ihren Daten vorkommt.

tr ' \n' '\n ' | sed 's/> />/g' | tr '\n ' ' \n'
Gilles 'SO - hör auf böse zu sein'
quelle
Beide Methoden werden hier bereits gezeigt, um in anderen Antworten eine bessere Wirkung zu erzielen. Und sein Ansatz mit sedgeht nicht ohne einen 2,5-Gigabyte-Puffer.
mikeserv
Hat jemand awk erwähnt? Oh, ich habe es verpasst, ich hatte Perl nur aus irgendeinem Grund in Terdons Antwort bemerkt. Niemand erwähnte den trAnsatz - mikeserv, Sie haben einen anderen (gültigen, aber weniger generischen) Ansatz gepostet, der zufällig auch verwendet wird tr.
Gilles 'SO- hör auf böse zu sein'
Gültige, aber weniger generische Klänge, wie Sie es gerade als funktionierende, gezielte Lösung bezeichnet haben. Ich denke, es ist schwer zu argumentieren, dass so etwas nicht nützlich ist, was seltsam ist, weil es 0 positive Stimmen hat. Der größte Unterschied, den ich zwischen meiner eigenen Lösung und Ihrem allgemeineren Angebot sehe , besteht darin, dass meine Lösung speziell ein Problem löst, während Ihre Lösung im Allgemeinen sein könnte. Das mag sich lohnen - und ich kann sogar meine Stimme umkehren -, aber es gibt auch die lästige Frage der 7 Stunden zwischen ihnen und das wiederkehrende Thema Ihrer Antworten, die andere imitieren. Kannst du das erklären?
mikeserv
1

was ist mit ed?

ed -s test.txt <<< $'/fruits/s/apple/banana/g\nw'

(via http://wiki.bash-hackers.org/howto/edit-ed )

andrej
quelle
bearbeitet, es gibt keine abhängigkeit mehr von der website
andrej
-1

Es gibt viele Möglichkeiten, dies zu tun, und die meisten hier sind wirklich gut, aber ich denke, dies ist mein Favorit:

tr '>\n' '\n>' | sed 's/^>*//;H;/./!d;x;y/\n>/>\n/'

Oder auch:

tr '>\n' '\n>' | sed 's/^>*//' | tr '\n>' '>\n'
mikeserv
quelle
Ich kann Ihre erste Antwort überhaupt nicht zum Laufen bringen. Während ich die Eleganz des zweiten bewundere, glaube ich, dass Sie das entfernen müssen *. So wie es jetzt ist, werden alle leeren Zeilen gelöscht, die auf eine Zeile folgen, die mit einem endet >. … Hmm. Wenn ich auf die Frage zurückblicke, sehe ich, dass sie etwas mehrdeutig ist. Die Frage lautet: "Ich möchte alle Zeilenumbrüche entfernen, die nach einem >, ... auftreten." Ich interpretiere das so, dass >\n\n\n\n\nfoodies in geändert werden sollte \n\n\n\nfoo, aber ich nehme an , dass dies foomöglicherweise die gewünschte Ausgabe ist.
Scott
@Scott - Ich habe Folgendes mit Variationen getestet: printf '>\n>\n\n>>\n>\n>>>\n>\nf\n\nff\n>\n' | tr '>\n' '\n>' | sed 's/^>*//;H;/./!d;x;y/\n>/>\n/'- Das ergibt >>>>>>>>>>f\n\nff\n\nfür mich die erste Antwort. Ich bin allerdings neugierig, was Sie tun, um es zu brechen, weil ich es reparieren möchte. Was den zweiten Punkt betrifft, stimme ich nicht zu, dass er mehrdeutig ist. Das OP fordert nicht auf, alle > vor einer \nE-Line stehenden E-Lines zu entfernen, sondern alle \n E-Lines nach a >.
mikeserv
1
Ja, aber eine gültige Interpretation ist, dass in >\n\n\n\n\nnur die erste Zeile nach a steht >; Alle anderen folgen anderen Zeilenumbrüchen. Beachten Sie, dass der Vorschlag des OP "das ist, was ich will, wenn es nur funktioniert" war sed -e 's/>\n/>/g', nicht sed -e 's/>\n*/>/g'.
Scott
1
@Scott - der Vorschlag hat nicht funktioniert und konnte es nie. Ich glaube nicht, dass der Codevorschlag von jemandem, der den Code nicht vollständig versteht, als gültiger Interpretationspunkt angesehen werden kann, da die einfache Sprache, die diese Person ebenfalls verwendet. Und außerdem die Ausgabe - wenn es tatsächlich funktioniert - von s/>\n/>/auf >\n\n\n\n\nwäre noch etwas , dass s/>\n/>/Would bearbeiten.
mikeserv