Как заменить несколько шаблонов сразу с помощью sed?

Предположим, что у меня строка abbc, и я хочу заменить:

  • ab → bc
  • bc → ab

Если я попробую заменить два, результат не то, что я хочу:

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

Итак, какую команду семени можно использовать для замены, как показано ниже?

echo abbc | sed SED_COMMAND
bcab

ИЗМЕНИТЬ: На самом деле текст может содержать более двух шаблонов, и я не знаю, сколько замен будет мне нужно. Поскольку был ответ, говорящий, что sed является редактором потока, и его замены жадно, я думаю, что мне нужно будет использовать для этого какой-то язык script.

Ответ 1

Возможно, что-то вроде этого:

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

Замените ~ символом, который, как вы знаете, не будет в строке.

Ответ 2

Это может сработать для вас (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

Это использует таблицу поиска, которая подготовлена ​​и удерживается в пространстве удержания (HS), а затем добавляется к каждой строке. Уникальный маркер (в данном случае \n) добавляется к началу строки и используется как метод для поиска по всей длине строки. Как только маркер достигает конца строки, процесс завершается и распечатывается таблица поиска и отбрасываются маркеры.

N.B. Таблица поиска подготовлена ​​в самом начале и второй уникальный маркер (в данном случае :), выбранный так, чтобы не столкнуться с строками подстановки.

С комментариями:

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
'

Таблица работает следующим образом:

   **   **   replacement
:abbc:bcab
 **   **     pattern

Ответ 3

Вот вариант ooga answer, который работает для поиска и замены пар без необходимости проверять, как значения могут быть повторно использованы:

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

Вот пример:

перед:

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

после

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

Обратите внимание, что \b обозначает границы слов, что мешает ________ мешать поиску (я использую GNU sed 4.2.2 на Ubuntu). Если вы не используете поиск по границе слова, тогда эта техника может не работать.

Также обратите внимание, что это дает те же результаты, что и удаление s/________//g и добавление && sed -i 's/________//g' path_to_your_files/*.txt до конца команды, но не требует указания пути дважды.

Общая вариация на это заключалась бы в использовании \x0 или _\x0_ вместо ________, если вы знаете, что в ваших файлах нет нулей, в качестве jthill предложил.

Ответ 4

sed - редактор потока. Он ищет и с жадностью заменяет. Единственный способ сделать то, что вы просили, - использовать промежуточный шаблон замещения и изменить его в конце.

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

Ответ 5

Tcl имеет встроенный для этого

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

Это работает, перемещая строку символа за раз, выполняя строковые сравнения, начиная с текущей позиции.

В 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

Ответ 6

Вот awk на основе oogas sed

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