Ersetzen Sie eine Zeichenfolge mit Zeilenumbrüchen

10

Mit der bashShell in einer Datei mit Zeilen wie den folgenden

first "line"
<second>line and so on

Ich möchte ein oder mehr Vorkommen von ersetzen "line"\n<second>mit other charactersund jedes Mal zu erhalten:

first other characters line and so on

Daher muss ich eine Zeichenfolge sowohl durch Sonderzeichen wie "und <als auch durch ein Zeilenumbruchzeichen ersetzen.

Nachdem ich zwischen den anderen Antworten gesucht hatte, stellte ich fest, dass sedZeilenumbrüche auf der rechten Seite des Befehls (also der other charactersZeichenfolge) akzeptiert werden können, nicht jedoch auf der linken Seite.

Gibt es eine Möglichkeit (einfacher als diese ), dieses Ergebnis mit sedoder zu erhalten grep?

BowPark
quelle
arbeitest du mit einem mac Die \newline Aussage, die Sie machen, ist, warum ich frage. Leute fragen selten, ob sie s//\n/mit GNU so gut wie möglich machen können sed, obwohl die meisten anderen seddiese Flucht auf der rechten Seite ablehnen. Trotzdem \nfunktioniert die Escape-Funktion in jedem POSIX links sedund Sie können sie portabel übersetzen, y/c/\n/obwohl sie den gleichen Effekt hat wie s/c/\n/gund daher nicht immer so nützlich ist.
Mikesserv

Antworten:

3

Drei verschiedene sedBefehle:

sed '$!N;s/"[^"]*"\n<[^>]*>/other characters /;P;D'

sed -e :n -e '$!N;s/"[^"]*"\n<[^>]*>/other characters /;tn'

sed -e :n -e '$!N;/"$/{$!bn' -e '};s/"[^"]*"\n<[^>]*>/other characters /g'

Sie alle drei bauen auf dem Grundbefehl s///ubstitution auf:

s/"[^"]*"\n<[^>]*>/other characters /

Sie alle versuchen auch, beim Umgang mit der letzten Zeile sedvorsichtig zu sein , da sie sich in Randfällen in Bezug auf ihre Ausgabe unterscheiden. Dies ist die Bedeutung $!einer Adresse, die mit jeder Zeile übereinstimmt, die !nicht die $letzte ist.

Sie alle verwenden auch den NBefehl ext, um die nächste Eingabezeile nach einem \nEwline-Zeichen an den Musterbereich anzuhängen. Jeder, der schon länger seddabei ist, wird gelernt haben, sich auf den \newline-Charakter zu verlassen - denn der einzige Weg, einen zu bekommen, besteht darin, ihn explizit dort abzulegen.

Alle drei versuchen, so wenig Eingaben wie möglich einzulesen, bevor sie Maßnahmen ergreifen. Sie handeln sedso schnell wie möglich und müssen vorher nicht die gesamte Eingabedatei einlesen.

Obwohl sie alle tun N, unterscheiden sich alle drei in ihren Rekursionsmethoden.

Erster Befehl

Der erste Befehl verwendet eine sehr einfache N;P;DSchleife. Diese drei Befehle sind in alle POSIX-kompatiblen Befehle integriert sedund ergänzen sich hervorragend.

  • N- Wie bereits erwähnt, wird die Next-Eingabezeile nach einem eingefügten \newline-Trennzeichen an den Musterraum angehängt.
  • P- wie p; Es Pdruckt den Musterraum - aber nur bis zum ersten vorkommenden \nEwline-Zeichen. Und so unter Berücksichtigung der folgenden Eingabe / Befehl:

    • printf %s\\n one two | sed '$!N;P;d'
  • sed Pspült nur einen . Mit ...

  • D- wie d; es Dlöst einen Musterraum aus und beginnt einen weiteren Linienzyklus. Im Gegensatz dazu d wird Dnur bis zur ersten auftretenden \nEwline im Musterraum gelöscht. Wenn nach dem \newline-Zeichen mehr Muster vorhanden ist , sedbeginnt der nächste Zeilenzyklus mit dem verbleibenden. Wenn die dim vorherigen Beispiel durch a ersetzt würden D, sedwürden beispielsweise Psowohl eins als auch zwei gedruckt .

Dieser Befehl wird nur für Zeilen wiederholt, die nicht mit der s///ubstitution-Anweisung übereinstimmen. Da die s///Ubstitution die \nhinzugefügte Ewline entfernt N, bleibt nie etwas übrig, wenn der Musterraum gelöscht wird sed D.

Es könnten Tests durchgeführt werden, um das Pund / oder Dselektiv anzuwenden , aber es gibt andere Befehle, die besser zu dieser Strategie passen. Da die Rekursion implementiert ist, um aufeinanderfolgende Zeilen zu verarbeiten, die nur einem Teil der Ersetzungsregel entsprechen, funktionieren aufeinanderfolgende Zeilenfolgen, die mit beiden Enden der s///Substitution übereinstimmen, nicht gut:

Angesichts dieser Eingabe:

first "line"
<second>"line"
<second>"line"
<second>line and so on

... es druckt ...

first other characters "line"
<second>other characters line and so on

Es funktioniert jedoch

first "line"
second "line"
<second>line

...Alles gut.

Zweiter Befehl

Dieser Befehl ist dem dritten sehr ähnlich. Beide verwenden ein :bRanch / tEst-Label (wie auch in der Antwort von Joeseph R. hier gezeigt wird ) und greifen unter bestimmten Bedingungen darauf zurück.

  • -e :n -e- Portable sedSkripte begrenzen eine :Label-Definition entweder mit einer \newline oder einer neuen inline -execution-Anweisung.
    • :n- definiert eine Bezeichnung mit dem Namen n. Dies kann jederzeit mit entweder bnoder zurückgegeben werden tn.
  • tn- Der tBefehl est kehrt zu einer angegebenen Bezeichnung zurück (oder beendet, falls keine angegeben ist, das Skript für den aktuellens/// Zeilenzyklus ), wenn eine Substitution vorliegt, da entweder die Bezeichnung definiert wurde oder sie zuletzt als tests erfolgreich bezeichnet wurde.

In diesem Befehl erfolgt die Rekursion für die übereinstimmenden Zeilen. Wenn seddas Muster erfolgreich durch andere Zeichen ersetzt wurde , sedkehrt es zur :nBeschriftung zurück und versucht es erneut. Wenn keine s///Substitution durchgeführt wird, seddruckt der Musterraum automatisch und beginnt den nächsten Zeilenzyklus.

Dies neigt dazu, aufeinanderfolgende Sequenzen besser zu handhaben. Wo der letzte fehlgeschlagen ist, wird Folgendes gedruckt:

first other characters other characters other characters line and so on

Dritter Befehl

Wie bereits erwähnt, ist die Logik hier der letzten sehr ähnlich, aber der Test ist expliziter.

  • /"$/bn- Das ist sedder Test. Da der bBefehl ranch eine Funktion dieser Adresse ist, sedwird erst nach dem Anhängen einer Ewline und dem Beenden des Musterbereichs mit einem doppelten Anführungszeichen eine bRanch wiederhergestellt .:n\n"

Zwischen Nund bwie möglich wird so wenig getan - auf diese Weise sedkönnen sehr schnell genau so viele Eingaben wie nötig gesammelt werden, um sicherzustellen, dass die folgende Zeile nicht mit Ihrer Regel übereinstimmt. Die s///Substitution unterscheidet sich hier darin, dass sie die gLobalflagge verwendet - und daher alle notwendigen Ersetzungen auf einmal vornimmt. Bei identischer Eingabe wird dieser Befehl identisch mit dem letzten ausgegeben.

mikeserv
quelle
Entschuldigen Sie die triviale Frage, aber was bedeutet das DATAund wie erhalten Sie die Texteingabe?
BowPark
@BowPark - In diesem Beispiel <<\DATA\ntext input\nDATA\nist eingebrannt, aber das ist nur Text, sedden die Shell in einem Dokument hier übergeben hat . Es würde genauso gut funktionieren wie sed 'script' filenameoder process that writes to stdout | sed 'script'. Hilft das?
Mikesserv
Ja, danke! Warum ist ohne Djede geänderte Zeile doppelt? (Sie haben es verwendet, wie es notwendig ist; vielleicht weiß ich es nicht sedsehr gut)
BowPark
1
@BowPark - Sie erhalten Doppel, wenn Sie das weglassen, Dda Dsonst Daus der Ausgabe das, was Sie jetzt sehen, verdoppelt wird. Ich habe gerade eine Bearbeitung vorgenommen - und ich kann das auch bald erweitern.
Mikesserv
1
@ BowPark - ok, ich habe es aktualisiert und Optionen bereitgestellt. Es könnte jetzt etwas einfacher zu lesen / verstehen sein. Ich habe mich auch ausdrücklich mit der DSache befasst.
Mikesserv
7

Nun, ich kann mir ein paar einfache Möglichkeiten vorstellen, aber keine beinhaltet grep(was sowieso keine Substitutionen macht) oder sed.

  1. Perl

    Um jedes Vorkommen von "line"\n<second>durch zu ersetzen other characters, verwenden Sie:

    $ perl -00pe 's/"line"\n<second>/other characters /g' file
    first other characters line and so on
    

    Oder verwenden Sie Folgendes, um mehrere aufeinanderfolgende Vorkommen "line"\n<second>als eins zu behandeln und alle durch ein einziges zu ersetzen other characters:

    perl -00pe 's/(?:"line"\n<second>)+/other characters /g' file
    

    Beispiel:

    $ cat file
    first "line"
    <second>"line"
    <second>"line"
    <second>line and so on
    $ perl -00pe 's/(?:"line"\n<second>)+/other characters /g' file
    first other characters line and so on
    

    Das -00bewirkt , dass die Datei in Perl „paragraph Modus“ lesen was bedeutet , dass „Linien“ von definiert sind , \n\nstatt \nim Wesentlichen jeder Absatz als Linie behandelt wird. Die Ersetzung stimmt daher über eine neue Zeile überein.

  2. awk

    $  awk -v RS="\n\n" -v ORS="" '{
          sub(/"line"\n<second>/,"other characters ", $0)
          print;
        }' file 
    first other characters line and so on
    

    Die gleiche Grundidee: Wir setzen das Datensatztrennzeichen ( RS) so \n\n, dass die gesamte Datei verschluckt wird, dann das Ausgabe-Datensatztrennzeichen auf nichts (andernfalls wird eine zusätzliche neue Zeile gedruckt) und verwenden dann die sub()Funktion, um die Ersetzung vorzunehmen.

terdon
quelle
2
@mikeserv? Welcher? Das zweite soll sein, das OP sagte, dass sie "ein oder mehrere Vorkommen von" ersetzen wollen, so dass das Essen des Absatzes durchaus das sein könnte, was sie erwarten.
Terdon
sehr guter Punkt. Ich denke, ich habe mich jedes Mal mehr darauf konzentriert und erhalten , aber ich denke, es ist nicht klar, ob dies ein Ersatz pro Auftreten oder ein Ersatz pro Folge von Ereignissen sein sollte ... @BowPark?
Mikesserv
Pro Vorkommen ist ein Ersatz erforderlich.
BowPark
@BowPark OK, dann sollte der erste Perl-Ansatz oder der awk beide funktionieren. Geben sie Ihnen nicht die gewünschte Ausgabe?
Terdon
Es funktioniert, danke, aber die dritte Zeile mit awksollte sein print;}' file. Ich muss Perl meiden und vorzugsweise verwenden sed, trotzdem haben Sie gute Alternativen vorgeschlagen.
BowPark
6

Lesen Sie die gesamte Datei und ersetzen Sie sie global:

sed -n 'H; ${x; s/"line"\n<second>/other characters /g; p}' <<END
first "line"
<second> line followed by "line"
<second> and last
END
first other characters  line followed by other characters  and last
Glenn Jackman
quelle
Ja. Es funktioniert, aber was ist, wenn ich mehrere Vorkommen habe?
BowPark
Huh, richtig. Feste
glenn Jackman
1
Es tut mir leid, dass ich nicht mehr picken muss, aber es ${cmds}ist GNU-spezifisch - die meisten anderen sederfordern eine \nEwline oder eine -ePause zwischen pund }. Sie können die Klammern insgesamt - und portabel - vermeiden und sogar vermeiden, ein zusätzliches \newline-Zeichen in die erste Zeile sed 'H;1h;$!d;x;s/"line"\n<second>/other characters /g'
einzufügen,
Ich habe es getestet und es scheint nicht tragbar. Es wird am Anfang der Ausgabe eine zusätzliche neue Zeile gedruckt, aber das Ergebnis ist auf GNU korrekt.
BowPark
So entfernen Sie den führenden Zeilenumbruch: sed -n '1{h;n};H; ${x; s/"line"\n<second>/other characters /g; p}'- Dies wird jedoch nicht mehr zu warten.
Glenn Jackman
3

Hier ist eine Variante von Glenns Antwort , die funktioniert, wenn Sie mehrere aufeinanderfolgende Vorkommen haben (funktioniert nur mit GNU sed):

sed ':x /"line"/N;s/"line"\n<second>/other characters/;/"line"/bx' your_file

Das :xist nur ein Etikett zum Verzweigen. Im Grunde bedeutet dies, dass die Zeile nach dem Ersetzen überprüft wird. Wenn sie immer noch übereinstimmt "line", verzweigt sie zum :xLabel zurück (das ist es, was dies bxtut), fügt dem Puffer eine weitere Zeile hinzu und beginnt mit der Verarbeitung.

Joseph R.
quelle
@mikeserv Bitte geben Sie genau an, was Sie meinen. Es hat bei mir funktioniert.
Joseph R.
@mikeserv Es tut mir leid, ich weiß wirklich nicht, wovon du sprichst. Ich habe die obige Codezeile zurück in mein Terminal kopiert und es hat korrekt funktioniert.
Joseph R.
1
zurückgezogen - dies funktioniert anscheinend in GNU, sedwo die Handhabung von Nicht-POSIX-Etiketten weit genug geht, um ein Leerzeichen als Trennzeichen für die Etikettendeklaration zu akzeptieren. Sie sollten jedoch beachten, dass jeder andere dort sedscheitern wird - und für scheitern wird N. GNU sedverstößt gegen die POSIX-Richtlinien zum Drucken des Musterbereichs vor dem Beenden in Nder letzten Zeile. POSIX macht jedoch deutlich, dass beim NLesen eines Befehls in der letzten Zeile nichts gedruckt werden sollte.
Mikeserv
Wenn Sie den Beitrag bearbeiten, um GNU anzugeben, werde ich meine Stimme umkehren und diese Kommentare löschen. Es könnte sich auch lohnen, etwas über den vBefehl von GNU zu lernen, der sich gegenseitig sedunterbricht, in GNU-Versionen 4 und höher jedoch nicht verfügbar ist.
Mikesserv
1
in diesem Fall werde ich noch eine anbieten - dies kann portabel gemacht werden wie : sed -e :x -e '/"line"/{$!N' -e '};s/"line"\n<second>/other characters/;/"line"/bx'.
Mikesserv