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


231

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

  • ab -> bc
  • bc -> ab

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

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

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

echo abbc | sed SED_COMMAND
bcab

РЕДАКТИРОВАТЬ : На самом деле текст может иметь более 2 шаблонов, и я не знаю, сколько замен мне понадобится. Поскольку был ответ о том, что sedэто потоковый редактор и его замены жадно, я думаю, что для этого мне понадобится некоторый язык сценариев.


Вам нужно сделать несколько замен на одной линии? Если нет, просто уберите gфлаг из обеих s///команд, и это сработает.
Этан Рейснер

Вы пропустили суть моего вопроса. Я имел в виду, нужно ли делать каждую замену более одного раза на одной линии. Существует ли более одного совпадения для ab или bc в исходном вводе.
Этан Рейснер

Извините @EtanReisner, я неправильно понял, ответ - да. текст может иметь несколько замен.
DaniloNC

Ответы:


342

Может быть, что-то вроде этого:

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

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


9
GNU Sed обрабатывает NULS, так что вы можете использовать \x0для ~~.
до

3
Является ли gнеобходимым и что он делает?
Ли

12
@Lee gдля global - он заменяет все экземпляры шаблона в каждой строке, а не только первый (это поведение по умолчанию).
ничто101

1
Пожалуйста, смотрите мой ответ stackoverflow.com/a/41273117/539149 для варианта ответа ooga, который может заменить несколько комбинаций одновременно.
Зак Моррис

3
что вы знаете, не будет в строке Для производственного кода, никогда не делайте никаких предположений о вводе. Что касается тестов, то тесты действительно никогда не подтверждают правильность, но хорошая идея для теста: использовать сам скрипт в качестве входных данных.
Гагелло

33

Я всегда использую несколько утверждений с "-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

Это добавит '\ n' перед всеми AND, GROUP BY, UNION и FROM, тогда как '&' означает совпавшую строку, а '\ n &' означает, что вы хотите заменить совпавшую строку на '\ n' перед 'совпавшим '


14

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

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 предложил .


Я согласен с комментарием Хагелло выше о том, что он не делает предположений о том, что может содержать вход. Поэтому лично я чувствую, что это самое надежное решение, кроме обвязки друг друга ( sed 's/ab/xy/' | sed 's/cd/ab/' .....)
leetbacoon

12

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

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


4

Это может работать для вас (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) добавляется к началу строки и используется в качестве метода увеличения поиска по всей длине строки. Как только маркер достигает конца строки, процесс завершается и распечатывается таблица поиска и маркеры отбрасываются.

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

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

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

Может быть более простым подходом для вхождения одного шаблона, вы можете попробовать, как показано ниже: echo 'abbc' | sed 's / ab / bc /; s / bc / ab / 2'

Мой вывод:

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

Для нескольких вхождений шаблона:

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

пример

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

Надеюсь это поможет !!


2

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

0

Вот awkоснованный на oogassed

echo 'abbc' | awk '{gsub(/ab/,"xy");gsub(/bc/,"ab");gsub(/xy/,"bc")}1'
bcab
Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.