Wie kann man mehrere Muster gleichzeitig durch sed ersetzen?

231

Angenommen, ich habe eine 'abbc'-Zeichenfolge und möchte Folgendes ersetzen:

  • ab -> bc
  • bc -> ab

Wenn ich zwei Ersetzungen versuche, ist das Ergebnis nicht das, was ich will:

echo 'abbc' | sed 's/ab/bc/g;s/bc/ab/g'
abab

Welchen sed-Befehl kann ich also wie unten beschrieben ersetzen?

echo abbc | sed SED_COMMAND
bcab

BEARBEITEN : Eigentlich könnte der Text mehr als 2 Muster haben und ich weiß nicht, wie viele Ersetzungen ich benötigen werde. Da es eine Antwort gab, die besagt, dass sedes sich um einen Stream-Editor handelt und seine Ersetzungen gierig sind, denke ich, dass ich dafür eine Skriptsprache verwenden muss.

DaniloNC
quelle
Müssen Sie mehrere Ersetzungen in derselben Leitung vornehmen? Wenn nicht, lassen Sie einfach das gFlag von diesen beiden s///Befehlen fallen und das wird funktionieren.
Etan Reisner
Sie haben den Punkt meiner Frage verpasst. Ich meinte, müssen Sie jeden Ersatz mehr als einmal in derselben Zeile vornehmen . Gibt es mehr als eine Übereinstimmung für ab oder bc in der ursprünglichen Eingabe.
Etan Reisner
Sorry @EtanReisner Ich habe falsch verstanden, Die Antwort ist ja. Der Text kann mehrfach ersetzt werden.
DaniloNC

Antworten:

341

Vielleicht so etwas:

sed 's/ab/~~/g; s/bc/ab/g; s/~~/bc/g'

Ersetzen Sie ~durch ein Zeichen, von dem Sie wissen, dass es nicht in der Zeichenfolge enthalten ist.

ooga
quelle
9
GNU sed behandelt Nuls, so dass Sie \x0für verwenden können ~~.
Bis zum
3
Ist gnotwendig und was macht es?
Lee
12
@Lee gist global - ersetzt alle Instanzen des Musters in jeder Zeile anstelle der ersten (dies ist das Standardverhalten).
naught101
1
In meiner Antwort stackoverflow.com/a/41273117/539149 finden Sie eine Variation der Antwort von ooga, die mehrere Kombinationen gleichzeitig ersetzen kann.
Zack Morris
3
dass Sie wissen, dass es nicht in der Zeichenfolge enthalten ist Nehmen Sie für Produktionscode niemals eine Annahme über die Eingabe vor. Für Tests beweisen Tests nie wirklich die Richtigkeit, aber eine gute Idee für einen Test ist: Verwenden Sie das Skript selbst als Eingabe.
Hagello
33

Ich benutze immer mehrere Anweisungen mit "-e"

$ sed -e 's:AND:\n&:g' -e 's:GROUP BY:\n&:g' -e 's:UNION:\n&:g' -e 's:FROM:\n&:g' file > readable.sql

Dies wird ein '\ n' vor allen ANDs, GROUP BYs, UNIONs und FROMs anhängen, während '&' die übereinstimmende Zeichenfolge bedeutet und '\ n &' bedeutet, dass Sie die übereinstimmende Zeichenfolge durch ein '\ n' vor dem 'übereinstimmenden' ersetzen möchten '

Paulo Henrique Lellis Gonalves
quelle
14

Hier ist eine Variation der Antwort von ooga , die für mehrere Such- und Ersetzungspaare funktioniert, ohne prüfen zu müssen, wie Werte wiederverwendet werden können:

sed -i '
s/\bAB\b/________BC________/g
s/\bBC\b/________CD________/g
s/________//g
' path_to_your_files/*.txt

Hier ist ein Beispiel:

Vor:

some text AB some more text "BC" and more text.

nach dem:

some text BC some more text "CD" and more text.

Beachten Sie, dass \bWortgrenzen bezeichnet werden, was das verhindert________ Suche gestört wird (ich verwende GNU sed 4.2.2 unter Ubuntu). Wenn Sie keine Wortgrenzensuche verwenden, funktioniert diese Technik möglicherweise nicht.

Beachten Sie auch, dass dies die gleichen Ergebnisse liefert wie das Entfernen des s/________//g und Anhängen&& sed -i 's/________//g' path_to_your_files/*.txt des Befehls an das Ende des Befehls, jedoch keine zweimalige Angabe des Pfads erforderlich macht.

Eine allgemeine Variante wäre die Verwendung \x0oder _\x0_anstelle von, ________wenn Sie wissen, dass in Ihren Dateien keine Nullen angezeigt werden, wie von jthill vorgeschlagen .

Zack Morris
quelle
Ich stimme dem obigen Kommentar von Hagello zu, dass keine Annahmen darüber getroffen werden, was die Eingabe enthalten könnte. Daher bin ich persönlich der Meinung, dass dies die zuverlässigste Lösung ist, abgesehen davon, dass Seds übereinander gelegt werden ( sed 's/ab/xy/' | sed 's/cd/ab/' .....)
Leetbacoon
12

sedist ein Stream-Editor. Es sucht und ersetzt gierig. Die einzige Möglichkeit, das zu tun, wonach Sie gefragt haben, besteht darin, ein Zwischensubstitutionsmuster zu verwenden und es am Ende wieder zu ändern.

echo 'abcd' | sed -e 's/ab/xy/;s/cd/ab/;s/xy/cd/'

kuriouscoder
quelle
4

Dies könnte für Sie funktionieren (GNU sed):

sed -r '1{x;s/^/:abbc:bcab/;x};G;s/^/\n/;:a;/\n\n/{P;d};s/\n(ab|bc)(.*\n.*:(\1)([^:]*))/\4\n\2/;ta;s/\n(.)/\1\n/;ta' file

Dies verwendet eine Nachschlagetabelle, die vorbereitet und im Haltebereich (HS) gehalten und dann an jede Zeile angehängt wird. Ein eindeutiger Marker (in diesem Fall\n ) wird dem Zeilenanfang vorangestellt und als Methode verwendet, um die Suche über die gesamte Länge der Zeile zu verfolgen. Sobald der Marker das Ende der Zeile erreicht hat, ist der Vorgang abgeschlossen und die Nachschlagetabelle und die verworfenen Marker werden ausgedruckt.

NB Die Nachschlagetabelle wird gleich zu Beginn vorbereitet und ein zweiter eindeutiger Marker (in diesem Fall :) ausgewählt, um nicht mit den Ersetzungszeichenfolgen in Konflikt zu geraten .

Mit einigen Kommentaren:

sed -r '
  # initialize hold with :abbc:bcab
  1 {
    x
    s/^/:abbc:bcab/
    x
  }

  G        # append hold to patt (after a \n)

  s/^/\n/  # prepend a \n

  :a

  /\n\n/ {
    P      # print patt up to first \n
    d      # delete patt & start next cycle
  }

  s/\n(ab|bc)(.*\n.*:(\1)([^:]*))/\4\n\2/
  ta       # goto a if sub occurred

  s/\n(.)/\1\n/  # move one char past the first \n
  ta       # goto a if sub occurred
'

Die Tabelle funktioniert folgendermaßen:

   **   **   replacement
:abbc:bcab
 **   **     pattern
Potong
quelle
3

Möglicherweise ist dies ein einfacherer Ansatz für das Auftreten einzelner Muster, den Sie wie folgt versuchen können: echo 'abbc' | sed 's / ab / bc /; s / bc / ab / 2'

Meine Ausgabe:

 ~# echo 'abbc' | sed 's/ab/bc/;s/bc/ab/2'
 bcab

Für mehrere Mustervorkommen:

sed 's/\(ab\)\(bc\)/\2\1/g'

Beispiel

~# cat try.txt
abbc abbc abbc
bcab abbc bcab
abbc abbc bcab

~# sed 's/\(ab\)\(bc\)/\2\1/g' try.txt
bcab bcab bcab
bcab bcab bcab
bcab bcab bcab

Hoffe das hilft !!

dst_91
quelle
2

Tcl hat einen eingebauten für diese

$ tclsh
% string map {ab bc bc ab} abbc
bcab

Dies funktioniert, indem Sie die Zeichenfolge zeichenweise durchlaufen und Zeichenfolgenvergleiche ab der aktuellen Position durchführen.

In Perl:

perl -E '
    sub string_map {
        my ($str, %map) = @_;
        my $i = 0;
        while ($i < length $str) {
          KEYS:
            for my $key (keys %map) {
                if (substr($str, $i, length $key) eq $key) {
                    substr($str, $i, length $key) = $map{$key};
                    $i += length($map{$key}) - 1;
                    last KEYS;
                }
            }
            $i++;
        }
        return $str;
    }
    say string_map("abbc", "ab"=>"bc", "bc"=>"ab");
'
bcab
Glenn Jackman
quelle
0

Hier basiert ein awkauf Oogas basierendessed

echo 'abbc' | awk '{gsub(/ab/,"xy");gsub(/bc/,"ab");gsub(/xy/,"bc")}1'
bcab
Jotne
quelle