#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, можете использовать justsed -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 для резервного копирования.