Jak zastąpić wiele wzorów naraz sed?

Załóżmy, że mam łańcuch 'abbc' i chcę go zastąpić:

  • ab - > bc
  • bc - > ab

Jeśli spróbuję dwa zastąpi 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 zastąpienia jak poniżej?

echo abbc | sed SED_COMMAND
bcab

EDIT : W rzeczywistości tekst może mieć więcej niż 2 wzory i nie wiem, ile zastąpień będę potrzebował. Ponieważ pojawiła się odpowiedź mówiąca, że sed jest edytorem strumieniowym, a jego zastąpienie jest chciwe, myślę, że będę potrzebował użyć do tego jakiegoś języka skryptowego.

Author: Spencer Kormos, 2014-10-26

7 answers

Może coś takiego:

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

Zastąp ~ znakiem, o którym wiesz, że nie będzie w łańcuchu.

 210
Author: ooga,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2014-10-26 01:41:07

Oto odmiana ooga ' s answer, która działa dla wielu par wyszukiwania i zamiany bez konieczności sprawdzania, jak 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 \b oznacza granice słów, co uniemożliwia ________ ingerowanie w wyszukiwanie (używam GNU sed 4.2.2 na Ubuntu). Jeśli nie używasz słowa boundary search, ta technika może nie praca.

Należy również zauważyć, że daje to takie same wyniki jak usunięcie s/________//g i dodanie && sed -i 's/________//g' path_to_your_files/*.txt na koniec polecenia, ale nie wymaga dwukrotnego podania ścieżki.

Ogólną wariacją na ten temat byłoby użycie \x0 lub _\x0_ zamiast ________, jeśli wiesz, że żadne nulle nie pojawiają się w Twoich plikach, , jak zasugerował jthill.

 9
Author: Zack Morris,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2017-05-23 11:47:26

sed jest edytorem strumieniowym. Wyszukuje i zastępuje chciwie. Jedynym sposobem, aby zrobić to, o co prosiłeś, jest użycie pośredniego wzoru substytucji i zmiana go z powrotem w końcu.

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

 4
Author: kuriouscoder,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2014-10-26 06:56:59

To może zadział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 tabeli wyszukiwania, która jest przygotowywana i przechowywana w przestrzeni przytrzymania (HS), a następnie dołączana do każdej linii. Unikalny znacznik (w tym przypadku \n) jest dołączany do początku linii i używany jako metoda przebijania-wzdłuż wyszukiwania na całej długości linii. Gdy znacznik dotrze do końca wiersza, proces zostanie zakończony i zostanie wydrukowana tabela wyszukiwania i znaczniki są odrzucane.

Uwaga: tabela wyszukiwania jest przygotowywany na samym początku i drugi unikalny znacznik (w tym przypadku :) wybrany tak, aby nie kolidował z ciągami substytucyjnymi.

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 tak:

   **   **   replacement
:abbc:bcab
 **   **     pattern
 4
Author: potong,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2014-10-26 15:07:55

Zawsze używam wielu poleceń 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

To dopisze '\n 'przed wszystkimi I' S, GROUP BY 's, UNION' S I FROM 's, podczas gdy' & 'oznacza dopasowany łańcuch, a' \n& 'oznacza, że chcesz zastąpić dopasowany łańcuch '\n 'przed' dopasowanym '

 2
Author: Paulo Henrique Lellis Gonalves,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2018-08-27 14:11:48

Tcl ma wbudowany do tego

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

Działa to poprzez chodzenie po łańcuchu znak w czasie robi porównywanie łańcuchów 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
 1
Author: glenn jackman,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2014-10-26 18:36:57

Oto awk na podstawie oogas sed

echo 'abbc' | awk '{gsub(/ab/,"xy");gsub(/bc/,"ab");gsub(/xy/,"bc")}1'
bcab
 -1
Author: Jotne,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2014-10-26 07:42:48