Регулярное выражение — вложенные шаблоны — внутри внешнего шаблона, но исключают внутренний шаблон

#regex #bash #sed #grep #pattern-matching

#регулярное выражение #bash #sed #grep #сопоставление с шаблоном

Вопрос:

У меня есть файл с приведенным ниже содержимым.

 <td> ${ dontReplaceMe } ReplaceMe ${dontReplaceMeEither} </td>
  

Я хочу сопоставить ‘ReplaceMe’, если он есть в теге td, но НЕ в том случае, если он находится в $ { … } выражение.

Могу ли я сделать это с помощью regex?

В настоящее время есть:

 sed '/${.*?ReplaceMe.*?}/!s/ReplaceMe/REPLACED/g' data.txt
  

Комментарии:

1. Какой-либо конкретный язык?

2. Я обновил теги. Чтобы ответить на ваш вопрос: bash scripting

3. Стив — тебе следует обновить свой первоначальный вопрос вместо того, чтобы задавать одно и то же снова (даже если он сформулирован немного по-другому). Спасибо.

4. Стив, я обновил свой ответ шаблоном, который работает для четырех самых тривиальных случаев. Я настоятельно не рекомендую вам его использовать!

Ответ №1:

Это невозможно.

Регулярное выражение может использоваться для языков Хомского 3-го типа (обычный язык).
Однако ваш пример кода является языком Хомского типа 2 (контекстно-свободный язык).

Практически сразу, как только задействован какой-либо вид вложенности (скобки), вы имеете дело с контекстно-свободными языками, которые не охватываются регулярными выражениями.

В принципе нет способа определить within a pair of x and y в регулярном выражении, поскольку для этого потребовалось бы, чтобы регулярное выражение имело какой-то стек, которого у него нет (будучи функционально эквивалентным автомату с конечным состоянием).


Задача брандицци — найти регулярное выражение, которое могло бы соответствовать хотя бы тривиальным случаям
Я на самом деле придумал этот (болезненно хакерский) шаблон регулярных выражений:

 perl -pe 's/(?<=<td>)((?:(?:{.*?})*[^{]*?)*)(ReplaceMe)(.*)(?=</td>)/$1REPLACED$3/g'
  

Он выполняет правильное (sic!) сопоставление для этих случаев:

 <td> ${ dontReplaceMe } ReplaceMe ${dontReplaceMeEither} </td>
<td> ReplaceMe ${dontReplaceMeEither} </td>
<td> ${ dontReplaceMe } ReplaceMe </td>
<td> ReplaceMe </td>
  

И сбой с этим (вложенность типа Хомского-2, помните? 😉 ):

 <td>${ ${ dontReplaceMe } ReplaceMe ${dontReplaceMeEither} }</td>
  

И это также не может заменить несколько совпадений:

 <td> ReplaceMe ReplaceMe </td>
<td> ReplaceMe ${dontReplaceMeEither} ReplaceMe </td>
  

Самой сложной частью было покрыть начало $ .
Это и предотвращение постоянного сбоя Реджинальда / Реджи при написании этого чудовища.

ЕЩЕ РАЗ: ЭКСПЕРИМЕНТАЛЬНО, НИКОГДА НЕ ИСПОЛЬЗУЙТЕ ЭТО В ПРОИЗВОДСТВЕННОМ КОДЕ!

(… или я выслежу вас, если мне когда-нибудь придется работать с вашим кодом / приложением 😉

Комментарии:

1. вы правы в некоторых моментах, которые следует учитывать в вашем ответе. 1. Если текст, который нужно выделить, довольно прост, то может быть практично сопоставить его с регулярным выражением, но я знаю, что вы это знаете 🙂 2. Некоторые регулярные выражения рекурсивны и могут соответствовать контекстно-свободным грамматикам (например, tinyurl.com/3jb2xqh ). 3. Хотя regexes не соответствует контекстно-свободным языкам, sed может соответствовать ему. На самом деле, я думаю, что sed даже соответствует Тьюрингу, поскольку он поддерживает конкатенацию и цикл. Я не верю, что это было бы очень практично, но это выглядит возможным. В любом случае, ваш ответ правильный и важный.

2. Стив спросил «Могу ли я сделать это с помощью regex?», на что ответ остался «нет» 😉 Однако вы высказали интересное замечание о sed. Не знал об этом, спасибо! 🙂 Что касается возможности регулярного выражения (небезопасно) сопоставлять простые случаи здесь: смотрите Мой ответ на обновления. 😉 Теперь я чувствую себя грязным. Извините, Ноам : (

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

Ответ №2:

Ну, для такого простого случая вам просто нужно убедиться, что строка не совпадает ${.*} :

 $ sed '/${.*}/!s/ReplaceMe/REPLACED/' input
<td> REPLACED </td>
<td> ${ don't ReplaceMe } </td>
  

! После /${.*}/ адреса sed отменяет критерии.

OTOH, если дело не так просто, я бы заподозрил, что ваша проблема будет сильно расти, и regex не будет лучшим решением.

Комментарии:

1. Существует ли эквивалент grep для не соответствует? Я хочу использовать grep для генерации списка файлов, по которым sed будет выполнять цикл.

2. @steve, вот что делает sed, вы можете передавать в sed несколько файлов. возможно, sed ‘s/MATCHTEXT / REPLACEDTEXT/g’ *.html будет перебирать все HTML-файлы. Это не подходит?

3. @Steve да, так и есть, это -v опция grep. Вы можете использовать это таким образом: ls | grep -v 'PATTERN' в списке отображаются только файлы, которые не совпадают. Если вы хотите применить к ним sed, можете использовать just sed -i.bkp 's/foo/bar/g' $(grep -v 'PATTERN') .

4. @matchew Я не уверен, но я думаю, что он хочет применить sed только к некоторым файлам, имя которых не соответствует какому-либо шаблону. Что ж, я бы порекомендовал @ Steve задать другой вопрос по этому поводу, если наши комментарии ему не помогли 🙂

5. Я задал другой вопрос в Q: 6272754. Я работаю с командой sed брандицци, и он может быть прав, что моя проблема намного сложнее. @matchew, спасибо. В конечном итоге мне нужно сделать мой sed рекурсивным (разделяя файлы внутри folder1/folder2/data.txt ). Я надеюсь, что seed сможет это сделать.

Ответ №3:

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

Комментарии:

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

2. я бы предложил любой инструмент, который может анализировать дерево dom. например, XML::DOM::Parser для perl и xml.dom.minidom.parse для python. затем примените более простое регулярное выражение к текстовым узлам внутри элементов dom, которые вы хотите, и забудьте о <td>s .

Ответ №4:

Что-то вроде <td>.*(?<!${).*ReplaceMe(?!.*}).*</td> должно сработать, если grep поддерживает отрицательный поиск (я не помню, поддерживает ли это).

Ответ №5:

 sed -i 's/<td>sReplaceMes</td>/<td>Replaced</td>/gi' input.file
  

сработало для меня.

вы можете рассмотреть возможность использования -i.bak для резервного копирования старого файла на случай ошибки.

альтернативно,

perl -pi -e 's/<td>sReplaceMes</td>/<td>Replaced</td>/g' temp

также работает, опять же, обратите внимание на -pi.bak для резервного копирования.