Почему sed не заменяет перекрывающиеся шаблоны - PullRequest
6 голосов
/ 14 сентября 2011

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

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

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

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

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

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

Ответы [ 5 ]

2 голосов
/ 14 сентября 2011

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

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

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

2 голосов
/ 14 сентября 2011

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

perl -pe 'while (s#\t\t#\t\n\t#) {}' <filename>
1 голос
/ 27 июня 2017

Не отличается от решения perl, для меня это работает с использованием чистого sed

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

Объяснение

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

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

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

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

Короткая версия

Который может быть сокращен до

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

1037 * MacOS * И версия для Mac (все еще совместимая с Linux / Windows): 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, что очень запутывает.

1 голос
/ 14 сентября 2011

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

  • s/^/ / вставка пробела в начале строки никогда не завершится
  • s/$/foo/ добавлением foo к каждой строке - аналогично
  • s/[A-Z][A-Z]*/CENSORED/ замена заглавных словЦЕНСОРА - аналогично

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

1 голос
/ 14 сентября 2011

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

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

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...