Czysto zamień wszystkie wystąpienia dwóch łańcuchów za pomocą sed


13

Załóżmy, że mam plik, który zawiera wiele wystąpień zarówno StringA, jak i StringB. Chcę zamienić wszystkie wystąpienia StringA na StringB i (jednocześnie) wszystkie wystąpienia StringB na StringA.

W tej chwili robię coś takiego

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

Problem z tym podejściem polega na tym, że zakłada, że ​​StringC nie występuje w pliku. Chociaż w praktyce nie jest to problemem, to rozwiązanie nadal wydaje się brudne - to znaczy, że jest to okazja do nauczenia się więcej magii uniksowej. :)

Odpowiedzi:


11

Jeśli StringBi StringAnie może pojawić się w tym samym wierszu wprowadzania, możesz powiedzieć sedowi, aby wykonał zamianę w jeden sposób, a spróbuj w inny sposób, jeśli nie wystąpiły żadne wystąpienia pierwszego szukanego ciągu.

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

W ogólnym przypadku nie sądzę, aby istniała łatwa metoda. Przy okazji zauważyć, że specyfikacja jest niejednoznaczna, czy StringAi StringBmogą się pokrywać. Oto rozwiązanie Perla, które zastępuje skrajne lewe wystąpienie dowolnego łańcucha i powtarza się.

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

Jeśli chcesz pozostać przy użyciu narzędzi POSIX, awk jest najlepszym rozwiązaniem. Awk nie ma prymitywu dla ogólnych parametryzowanych zamienników, więc musisz rzucić własne.

<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
}'

Kiedy uruchamiam pierwsze polecenie, sed mówi mi sed: can't read s/StringB/StringA/g: No such file or directory. Wygląda na -e t PATTERNto, że nie jest dobrze zrozumiany.
Gyscos

1
@Gyscos Brakowało -eprzed drugim spoleceniem. Naprawiłem swoją odpowiedź.
Gilles „SO- przestań być zły”

8

W tej chwili robię coś takiego
...
Problem z tym podejściem polega na tym, że zakłada, że ​​StringC nie występuje w pliku.

Myślę, że twoje podejście jest w porządku, powinieneś po prostu użyć czegoś innego zamiast łańcucha, czegoś, co nie może wystąpić w linii (w przestrzeni wzorów). Najlepszym kandydatem jest \newline.
Zwykle żadna linia wejściowa w obszarze wzorców nie będzie zawierała tego znaku, więc aby zamienić wszystkie wystąpienia THISiw THATpliku, możesz uruchomić:

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

lub, jeśli twój sed obsługuje również \nw RHS:

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

1
To jest piękne. Trochę płakałam. Innym sposobem wykonania nowej linii RHS są zmienne powłoki - niezależnie od tego, czy sedobsługuje pewne znaki ucieczki, czy nie, staje się o wiele mniej ważne, jeśli wcześniej przygotujesz kilka makr. Jak set /THIS /THAT "$(printf \\n/)"; sed "s/$2/\\$4g;s/$3$2/g;s/\\n$3/g"- trochę głupio tutaj, co prawda, ale ma to o wiele więcej sensu, kiedy indziej - szczególnie dla klas char i podobnych.
mikeserv

Co powiesz na to, stary. Jest nawet odpowiedź na ten temat. Czy było tam, kiedy skomentowałem? Właśnie widziałem, jak ta rzecz wyskakuje na ostatnio edytowanej liście (być może), a górna linia najwyższej odpowiedzi była trochę nieaktualna (chyba tylko jeśli chodzi o niewbudowanego linuxa) . Wolę sugestię Gillesa - jeśli nie robisz długiego biegu sed, ciągły widelec nad głową ejest koszmarem kinduva. Z innej strony - bawię się pasteprzez cały dzień. Zrobiłem parser opcji - podobny columndo. Po prostu generuje myślniki dla ciągów wejściowych i ciągów razem.
mikeserv

3

Uważam, że użycie łańcucha „nonce” do zamiany dwóch słów jest całkowicie poprawne. Jeśli chcesz bardziej ogólnego rozwiązania, możesz zrobić coś takiego:

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

To daje

say me say you

Zauważ, że potrzebujesz tutaj dwóch dodatkowych podstawień, aby uniknąć zastąpienia, x_xjeśli zdarzy się, że będziesz mieć ciąg „x_x”. Ale nawet to wydaje mi się prostsze niż awkrozwiązanie.


Wydaje się, że tak właśnie pytał Pytający.
roaima,

1
Tak, przeoczyłem to na początku (patrz historia edycji), ale moje podane rozwiązanie jest inne, ponieważ działa nawet wtedy, gdy ciąg zastępujący (tutaj „x_x”) występuje w oryginalnym ciągu, stąd jest bardziej ogólny.
David Ongaro,

Sprytne, ale jest pewien haczyk. Jeśli StringA lub StringB zawiera _, należy dostosować _sam (wybierz inny znak) lub kłopotliwy ciąg (wykonaj s/_/__/gna nim wcześniej, wydaje się lepszy). Tego rozwiązania nie można na ślepo zastosować do zamiany dowolnych ciągów.
Kamil Maciorowski,

@KamilMaciorowski Nie rozumiem, co masz na myśli? Właściwie s/_/__/gaplikuję wcześniej. Może po prostu pokaż testową, która się nie powiedzie.
David Ongaro,

@KamilMaciorowski ah Myślę, że teraz rozumiem. Masz na myśli, jeśli sam ciągi zamienne zawierają _, więc powiedzieć, zastępując y_ouz me. Tak, to prawda, trzeba być tego świadomym i wyrazić y__outo. Skrypt uwzględniający zastąpienie jako parametry wejściowe również musi to uwzględnić.
David Ongaro,
Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.