Почему sed не заменяет перекрывающиеся шаблоны

У меня есть файл выгрузки базы данных с полем, разделенным символом ‹TAB>. Я запускаю этот файл через sed, чтобы заменить любые вхождения ‹TAB>‹TAB> на ‹TAB>\N‹TAB>. Это связано с тем, что когда файл загружается в MySQL, \N интерпретируется как NULL.

Команда sed 's/\t\t/\t\N\t/g;' почти работает, за исключением того, что он заменяет только первый экземпляр, например. "...‹TAB>‹TAB>‹TAB>..." становится "...‹TAB>\N‹TAB>‹TAB>...".

Если я использую 's/\t\t/\t\N\t/g;s/\t\t/\t\N\t/g;' он заменяет больше экземпляров.

У меня есть мнение, что, несмотря на модификатор /g, это как-то связано с тем, что конец одного совпадения является началом другого.

Может ли кто-нибудь объяснить, что происходит, и предложить команду sed, которая будет работать, или мне нужно зациклиться.

Я знаю, что, вероятно, мог бы переключиться на awk, perl, python, но я хочу знать, что происходит в sed.


person hairyone    schedule 14.09.2011    source источник
comment
Если бы вы могли указать очевидный ответ на то, что должно произойти, когда замены перекрываются, я уверен, что это привлекло бы много внимания. Что произойдет в вашем параллельном sed, если ввод будет a и будут запрошены замены s/a/b/&s/b/a/? (Я использую & в качестве разделителя команд, чтобы указать, что обе операции должны выполняться одновременно.)   -  person tripleee    schedule 14.09.2011
comment
Еще одна проблема — бесконечный цикл: s/something/something or other/ и sed выполняются очень долго (точнее, не хватает памяти)!   -  person Jonathan Leffler    schedule 14.09.2011
comment
Спасибо @triplee, когда вы так говорите, становится совершенно очевидным, если учесть, что повторное сканирование строки может привести к циклу.   -  person hairyone    schedule 14.09.2011
comment
Следует отметить, что если \t=a и \N=b, то aaa в первой подстановке будет заменено на abaa, а во второй - на ababa. Таким образом, для замены всех случаев не требуется никакого цикла, достаточно двух приложений одной и той же глобальной замены.   -  person potong    schedule 04.07.2020


Ответы (5)


Я знаю, что вы хотите sed, но sed это совсем не нравится, кажется, что конкретно (см. здесь) не будет делать то, что вы хотите. Однако Perl сделает это (AFAIK):

perl -pe 'while (s#\t\t#\t\n\t#) {}' <filename>
person KevinDTimm    schedule 14.09.2011
comment
Может быть, это делается для того, чтобы конечные нули не считались пустыми строками? (обратите внимание, что я заменил \n на \\N) perl -pe 'while (s#\t\t#\t\\Nt#) {}' -pe 's/\t$/\t\\N/ грамм' - person Seamus Abshere; 27.10.2011
comment
Perl поддерживает положительный просмотр вперед, поэтому perl -pe 's|\t(?=\t)|\t\n|g' работает - person Andy; 28.06.2017

В качестве обходного пути замените каждую вкладку на tab + \N; затем удалите все вхождения \N, за которыми сразу не следует табуляция.

sed -e 's/\t/\t\\N/g' -e 's/\\N\([^\t]\)/\1/g'

... при условии, что ваш sed использует обратную косую черту перед группировкой круглых скобок (существуют диалекты sed, которым не нужна обратная косая черта; попробуйте без них, если это не работает для вас.)

person tripleee    schedule 14.09.2011

Не отличается от решения Perl, это работает для меня, используя чистый sed

С улучшением @Robin A. Meade

sed ':repeat;
     s|\t\t|\t\n\t|g;
     t repeat'

Объяснение

  • :repeat — это метка, используемая для команд ветвления, аналогичная пакетной
  • s|\t\t|\t\n\t|g; - Стандартная замена 2 вкладок на вкладку-новая строка-вкладка. Я по-прежнему использую глобальный флаг, потому что если у вас, скажем, 15 вкладок, вам нужно будет зациклиться только дважды, а не 14 раз.
  • t repeat означает, что если команда s произвела какие-либо замены, то переходит к метке repeat, в противном случае она переходит на следующую строку и начинается заново.

Так что это происходит так. Продолжайте повторять (перейдите к repeat), пока не будет найдено совпадение с шаблоном из двух вкладок.

Хотя можно возразить, что можно просто сделать две одинаковые глобальные замены и назвать это хорошим, этот же метод может работать и в более сложных сценариях.

Как указывает @thorn-blake, sed просто не поддерживает расширенные функции, такие как просмотр вперед, поэтому вам нужно сделать такой цикл.

Исходный ответ

sed ':repeat;
     /\t\t/{
       s|\t\t|\t\n\t|g;
       b repeat
     }'

Объяснение

  • :repeat — это метка, используемая для команд ветвления, аналогичная пакетной
  • /\t\t/ означает совпадение вкладок шаблона 2. Если шаблон совпал, выполняется команда, следующая за второй /.
  • {} - В этом случае команда, следующая за командой match, является группой. Таким образом, все команды в группе выполняются, если встречается шаблон соответствия.
  • s|\t\t|\t\n\t|g; - Стандартная замена 2 вкладок на вкладку-новая строка-вкладка. Я по-прежнему использую глобальный, потому что если у вас, скажем, 15 вкладок, вам нужно будет выполнить цикл только дважды, а не 14 раз.
  • b repeat означает всегда переходить (ветвь) к метке repeat

Укороченная версия

Который можно сократить до

sed ':r;s|\t\t|\t\n\t|g; t r'

# Original answer
# sed ':r;/\t\t/{s|\t\t|\t\n\t|g; b r}'

MacOS

И версия для Mac (но все еще совместимая с Linux/Windows):

sed $':r\ns|\t\t|\t\\\n\t|g; t r'

# Original answer
# sed $':r\n/\t\t/{ s|\t\t|\t\\\n\t|g; b r\n}'
  • Вкладки должны быть буквальными в BSD sed
  • Новые строки должны быть как буквальными, так и экранированными одновременно, поэтому одинарная косая черта (это \ до того, как она будет обработана $, что делает ее одной буквальной косой чертой) плюс \n, который становится фактической новой строкой.
  • И имена меток (:r), и команды ветвления (b r, если не конец выражения) должны заканчиваться новой строкой. Специальные символы, такие как точки с запятой и пробелы, используются командой label name/branch в BSD, что делает ее очень запутанной.
person Andy    schedule 27.06.2017
comment
Можно ли упростить ваше решение до sed ':repeat; s|\t\t|\t\n\t|g; t repeat', изменив Команда ветвления с безусловной, b, на условную, t. - person Robin A. Meade; 21.12.2020

Правильно, даже с /g sed не будет соответствовать тексту, который он заменил снова. Таким образом, он считывает <TAB><TAB> и выводит <TAB>\N<TAB>, а затем считывает следующее из входного потока. См. http://www.grymoire.com/Unix/Sed.html#uh- 7

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

person Tamzin Blake    schedule 14.09.2011

Что ж, sed просто работает так, как задумано. Строка ввода сканируется один раз, а не несколько раз. Может быть, полезно посмотреть на последствия, если sed использовал повторное сканирование входной строки для обработки перекрывающихся шаблонов по умолчанию: в этом случае даже простые замены будут работать совершенно по-другому - некоторые могут сказать, что это нелогично - например.

  • s/^/ / вставка пробела в начале строки никогда не завершалась
  • s/$/foo/ добавление foo к каждой строке - аналогично
  • s/[A-Z][A-Z]*/CENSORED/ замена слов в верхнем регистре на CENSORED - аналогично

Вероятно, есть много других ситуаций. Конечно, все это можно исправить, скажем, с помощью модификатора замены, но во время разработки sed было выбрано текущее поведение.

person Jens    schedule 14.09.2011