Tauschen Sie alle Vorkommen von zwei Saiten mit sed sauber aus


13

Angenommen, ich habe eine Datei, die mehrere Vorkommen von StringA und StringB enthält. Ich möchte alle Vorkommen von StringA durch StringB und (gleichzeitig) alle Vorkommen von StringB durch StringA ersetzen.

Im Moment mache ich so etwas wie

cat file.txt | sed 's/StringB/StringC/g' | sed 's/StringA/StringB/g' | sed 's/StringC/StringA/g'

Das Problem bei diesem Ansatz ist, dass davon ausgegangen wird, dass StringC in der Datei nicht vorkommt. Obwohl dies in der Praxis kein Problem ist, fühlt sich diese Lösung immer noch schmutzig an - das heißt, es ist eine Gelegenheit, mehr über Unix-Magie zu lernen. :)

Antworten:


11

Wenn StringBund StringAnicht in derselben Eingabezeile angezeigt werden können, können Sie sed anweisen, die Ersetzung in die eine und die andere Richtung durchzuführen, und es nur dann versuchen, wenn die erste gesuchte Zeichenfolge nicht vorkommt.

<file.txt sed -e 's/StringA/StringB/g' -e t -e 's/StringB/StringA/g'

Im Allgemeinen glaube ich nicht, dass es in sed eine einfache Methode gibt. By the way, zur Kenntnis , dass die Spezifikation ist nicht eindeutig , ob StringAund StringBkönnen sich überlappen. Hier ist eine Perl-Lösung, die das Vorkommen einer der beiden Zeichenfolgen ganz links ersetzt und wiederholt.

<file.txt perl -pe 'BEGIN {%r = ("StringA" => "StringB", "StringB" => "StringA")}
                    s/(StringA|StringB)/$r{$1}/ge'

Wenn Sie bei POSIX-Tools bleiben möchten, ist awk der richtige Weg. Awk hat kein Primitiv für allgemeine parametrisierte Ersetzungen, Sie müssen also Ihr eigenes würfeln.

<file.txt awk '{
    while (match($0, /StringA|StringB/)) {
        printf "%s", substr($0, 1, RSTART-1);
        $0 = substr($0, RSTART);
        printf "%s", /^StringA/ ? "StringB" : "StringA";
        $0 = substr($0, 1+RLENGTH)
    }
    print
}'

Wenn ich den ersten Befehl ausführe, sagt sed es mir sed: can't read s/StringB/StringA/g: No such file or directory. Es scheint -e t PATTERNnicht gut verstanden zu sein.
Gyscos

1
@Gyscos -eVor dem zweiten sBefehl fehlte ein . Ich habe meine Antwort korrigiert.
Gilles 'SO- hör auf, böse zu sein'

8

Im Moment mache ich so etwas wie
...............
Das Problem bei diesem Ansatz ist, dass davon ausgegangen wird, dass StringC in der Datei nicht vorkommt.

Ich denke, Ihr Ansatz ist in Ordnung, Sie sollten statt eines Strings einfach etwas anderes verwenden, etwas, das in einer Zeile (im Musterraum) nicht vorkommen kann. Der beste Kandidat ist die \newline.
Normalerweise enthält keine Eingabezeile im Musterbereich dieses Zeichen. Um alle Vorkommen von THISund THATin einer Datei auszutauschen, können Sie Folgendes ausführen:

sed 's/THIS/\
/g
s/THAT/THIS/g
s/\n/THAT/g' infile

oder, wenn Ihr Sed auch \nin der RHS unterstützt :

sed 's/THIS/\n/g;s/THAT/THIS/g;s/\n/THAT/g' infile

1
Dies ist schön. Ich weinte ein bisschen. Eine andere Möglichkeit, die RHS-Zeilenumbrüche auszuführen, sind Shell-Variablen - ob die sedbestimmte Escape-Zeichen unterstützen oder nicht, wird viel weniger wichtig, wenn Sie zuvor einige Makros vorbereiten. Wie set /THIS /THAT "$(printf \\n/)"; sed "s/$2/\\$4g;s/$3$2/g;s/\\n$3/g"- zugegebenermaßen ein bisschen dumm hier, aber es ist viel sinnvoller, wenn es ein anderes Mal ist - besonders für Zeichenklassen und ähnliches.
mikeserv

Wie wäre es damit, Mann? Es gibt sogar eine Antwort darauf. War es dort, als ich den Kommentar abgegeben habe? Ich habe gerade gesehen, wie das Ding in der kürzlich bearbeiteten Liste auftauchte (vielleicht) und die oberste Zeile der obersten Antwort war ein wenig falsch (wenn Sie sich nur für nicht eingebettetes Linux interessieren, denke ich) . Ich bevorzuge Gilles 'Vorschlag dort - es sei denn, Sie machen einen langen Lauf sed, der ständige Aufwand eist ein Alptraum. Anders gesagt - ich habe pasteeinen ganzen Tag lang damit gespielt. Ich habe eine Option gemacht, die parserartig ist column. Es werden nur Striche für Eingabe-Strings und Strings-Stuff zusammengefügt.
mikeserv

3

Ich denke, es ist vollkommen richtig, eine "Nonce" -String zu verwenden, um zwei Wörter zu tauschen. Wenn Sie eine allgemeinere Lösung wünschen, können Sie Folgendes tun:

sed 's/_/__/g; s/you/x_x/g; s/me/you/g; s/x_x/me/g; s/__/_/g' <<<"say you say me"

Das ergibt

say me say you

Beachten Sie, dass Sie hier die zwei zusätzlichen Ersetzungen benötigen, um zu vermeiden, x_xdass Sie sie ersetzen, wenn Sie zufällig "x_x" -Strings haben. Aber auch das scheint awkmir einfacher zu sein als die Lösung.


Das scheint das zu sein, was die Asker gesagt haben.
Roaima

1
Ja, das habe ich zuerst übersehen (siehe Bearbeitungsverlauf), aber meine angegebene Lösung ist anders, da sie auch dann funktioniert, wenn die Ersatzzeichenfolge (hier "x_x") in der Originalzeichenfolge vorkommt, weshalb sie allgemeiner ist.
David Ongaro

Klug, aber es gibt einen Haken. Wenn StringA oder StringB enthalten ist _, muss man die _Zeichenfolge selbst (wählen Sie ein anderes Zeichen) oder die problematische Zeichenfolge (führen Sie s/_/__/gsie vorher aus, scheint besser zu sein) anpassen . Ihre Lösung kann nicht blind angewendet werden, um beliebige Zeichenfolgen auszutauschen.
Kamil Maciorowski

@KamilMaciorowski Ich verstehe nicht, was du meinst? Ich bewerbe mich eigentlich s/_/__/gvorher. Vielleicht zeigen Sie einfach einen Testfall, der fehlschlägt.
David Ongaro

@KamilMaciorowski ah ich glaube ich verstehe jetzt. Sie meinen, wenn die Ersetzungszeichenfolgen selbst ein enthalten _, sagen Sie Ersetzen y_oudurch me. Ja, es ist wahr, man muss sich dessen bewusst sein und es y__ouin den Ausdruck bringen. Ein Skript, das die Ersetzung als Eingabeparameter verwendet, muss dies ebenfalls berücksichtigen.
David Ongaro
Durch die Nutzung unserer Website bestätigen Sie, dass Sie unsere Cookie-Richtlinie und Datenschutzrichtlinie gelesen und verstanden haben.
Licensed under cc by-sa 3.0 with attribution required.