it-swarm.com.de

Wie ersetze ich mehrere Muster gleichzeitig durch sed?

Angenommen, ich habe die Zeichenfolge 'abbc' 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 verwenden, um wie folgt zu ersetzen?

echo abbc | sed SED_COMMAND
bcab

EDIT: Eigentlich könnte der Text mehr als 2 Muster haben und ich weiß nicht, wie viele Ersetzungen ich brauchen werde. Da es eine Antwort gab, die besagt, dass sed ein Stream-Editor ist und seine Ersetzungen gierig sind, denke ich, dass ich dafür eine Skriptsprache verwenden muss.

175
DaniloNC

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.

272
ooga

Ich verwende 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 AND's, GROUP BY's, UNION's und FROM's anhängen, wohingegen '&' die übereinstimmende Zeichenkette bedeutet und '\ n &' bedeutet, dass Sie die übereinstimmende Zeichenkette durch ein '\ n' vor dem 'übereinstimmenden ersetzen möchten '

Hier ist eine Variation von Oogas Antwort , die für das mehrfache Suchen und Ersetzen von Paaren funktioniert, ohne dass geprüft werden muss, wie Werte wiederverwendet werden könnten:

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:

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

Beachten Sie, dass \b Wortgrenzen kennzeichnet, wodurch verhindert wird, dass ________ die Suche stört (ich verwende GNU sed 4.2.2 unter Ubuntu). Wenn Sie keine Wortbegrenzungssuche verwenden, funktioniert diese Technik möglicherweise nicht.

Beachten Sie außerdem, dass dies zu den gleichen Ergebnissen führt wie das Entfernen von s/________//g und das Anhängen von && sed -i 's/________//g' path_to_your_files/*.txt an das Ende des Befehls, wobei jedoch der Pfad nicht zweimal angegeben werden muss.

Eine allgemeine Variante wäre, \x0 oder _\x0_ anstelle von ________ zu verwenden, wenn Sie wissen, dass Ihre Dateien keine Nullen enthalten, wie noch empfohlen .

10
Zack Morris

sed ist 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/'

7
kuriouscoder

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 im Hold Space (HS) vorbereitet und 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 Zeilenlänge hinweg fortzusetzen. Sobald die Markierung das Ende der Zeile erreicht, ist der Vorgang abgeschlossen und die Nachschlagetabelle und die zu verwerfenden Markierungen werden ausgedruckt.

N.B. Die Nachschlagetabelle wird 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 so:

   **   **   replacement
:abbc:bcab
 **   **     pattern
4
potong

Tcl hat dafür ein builtin

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

Dies funktioniert, indem die Zeichenfolge Zeichen für Zeichen ab der aktuellen Position verglichen wird.

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
2
glenn jackman

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

Bei mehreren Vorkommen eines Musters:

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 !!

1
dst_91

Hier ist ein awk basierend auf Oogas sed

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