Jak zastąpić wiele wzorów naraz?

231

Załóżmy, że mam ciąg „abbc” i chcę zastąpić:

  • ab -> bc
  • bc -> ab

Jeśli spróbuję dwóch zamień, wynik nie jest tym, czego chcę:

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

Więc jakiej komendy sed mogę użyć do zamiany, jak poniżej?

echo abbc | sed SED_COMMAND
bcab

EDYCJA : W rzeczywistości tekst może mieć więcej niż 2 wzory i nie wiem, ile będzie potrzebnych zamienników. Ponieważ pojawiła się odpowiedź mówiąca, że sedjest to edytor strumieniowy, a jego zamienniki są zachłannie, myślę, że będę musiał do tego użyć jakiegoś języka skryptowego.

DaniloNC
źródło
Czy potrzebujesz wielu zamienników w tej samej linii? Jeśli nie, po prostu upuść gflagę z obu tych s///poleceń i to zadziała.
Etan Reisner
Nie trafiłeś w sedno mojego pytania. Chodzi mi o to, czy musisz dokonywać każdej wymiany więcej niż raz na tej samej linii. Czy istnieje więcej niż jedno dopasowanie dla oryginalnego wejścia ab lub dla bcniego
Etan Reisner
Przepraszam @EtanReisner, źle zrozumiałem, odpowiedź jest twierdząca. tekst może mieć wiele zamienników.
DaniloNC

Odpowiedzi:

342

Może coś takiego:

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

Zamień ~na znak, o którym wiesz, że nie będzie go w ciągu.

ooga
źródło
9
GNU sed uchwyty nuls, więc można go używać \x0do ~~.
jthill
3
Jest gkonieczne i co to robi?
Lee
12
@Lee gjest globalny - zastępuje wszystkie wystąpienia wzorca w każdej linii, a nie tylko pierwszy (co jest zachowaniem domyślnym).
naught101
1
Zapoznaj się z moją odpowiedzią stackoverflow.com/a/41273117/539149, aby uzyskać odmianę odpowiedzi ooga, która może zastąpić wiele kombinacji jednocześnie.
Zack Morris,
3
o których wiesz, że nie będzie w ciągu W przypadku kodu produkcyjnego nigdy nie zakładaj żadnych danych wejściowych. W przypadku testów, testy nigdy nie dowodzą poprawności, ale dobrym pomysłem na test jest: Użyj samego skryptu jako danych wejściowych.
hagello
33

Zawsze używam wielu instrukcji z „-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

Spowoduje to dodanie „\ n” przed wszystkimi AND, GROUP BY, UNION i FROM, podczas gdy „&” oznacza dopasowany ciąg, a „\ n &” oznacza, że ​​chcesz zastąpić dopasowany ciąg „\ n” przed „dopasowanym” „

Paulo Henrique Lellis Gonalves
źródło
14

Oto odmiana odpowiedzi ooga, która działa w przypadku wielokrotnego wyszukiwania i zamiany par bez konieczności sprawdzania, w jaki sposób wartości mogą być ponownie użyte:

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

Oto przykład:

przed:

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

po:

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

Zauważ, że \boznacza granice słów, co zapobiega________ ingerowaniu w wyszukiwanie (używam GNU sed 4.2.2 na Ubuntu). Jeśli nie używasz wyszukiwania granicy słów, ta technika może nie działać.

Zauważ też, że daje to takie same wyniki jak usunięcie s/________//gi dopisanie && sed -i 's/________//g' path_to_your_files/*.txtna końcu polecenia, ale nie wymaga dwukrotnego podania ścieżki.

Ogólną odmianą tego byłoby użycie \x0lub _\x0_zamiast tego, ________jeśli wiesz, że w twoich plikach nie ma żadnych zer , jak sugerował jthill .

Zack Morris
źródło
Zgadzam się z powyższym komentarzem Hagello, że nie poczyniłem założeń co może zawierać dane wejściowe. Dlatego osobiście uważam, że jest to najbardziej niezawodne rozwiązanie, poza nakładaniem na siebie sed 's/ab/xy/' | sed 's/cd/ab/' .....
szwów
12

sedjest edytorem strumieniowym. Chciwie wyszukuje i zastępuje. Jedynym sposobem na zrobienie tego, o co prosiłeś, jest użycie pośredniego wzorca podstawienia i zmiana go z powrotem na końcu.

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

kuriouscoder
źródło
4

Może to działać dla ciebie (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

Używa to tabeli odnośników, która jest przygotowywana i trzymana w przestrzeni wstrzymania (HS), a następnie dołączana do każdej linii. Unikalny znacznik (w tym przypadku\n ) jest dodawany na początku linii i stosowany jako metoda przewijania wyszukiwania wzdłuż całej linii. Gdy znacznik dotrze do końca linii, proces jest zakończony i drukowana jest tablica przeglądowa i odrzucane znaczniki.

Uwaga: Tablica odnośników jest przygotowana na samym początku, a drugi unikatowy znacznik (w tym przypadku :) jest wybierany, aby nie kolidował z ciągami podstawień.

Z kilkoma komentarzami:

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
'

Tabela działa w następujący sposób:

   **   **   replacement
:abbc:bcab
 **   **     pattern
potong
źródło
3

Może być prostsze podejście do wystąpienia pojedynczego wzorca, który możesz wypróbować w następujący sposób: echo 'abbc' | sed 's / ab / bc /; s / bc / ab / 2'

Moja produkcja:

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

W przypadku wielu wystąpień wzoru:

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

Przykład

~# 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

Mam nadzieję że to pomoże !!

dst_91
źródło
2

Tcl ma wbudowane do tego celu

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

Działa to poprzez chodzenie po łańcuchu po znaku, dokonując porównań łańcucha, zaczynając od bieżącej pozycji.

W perlu:

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
źródło
0

Oto awkoparty na oogassed

echo 'abbc' | awk '{gsub(/ab/,"xy");gsub(/bc/,"ab");gsub(/xy/,"bc")}1'
bcab
Jotne
źródło