Не жадібна відповідність з регулярним виразом SED (емуляція perl's. *?)


22

Я хочу використовувати sedдля заміни будь-чого в рядку між першим ABі першим виникненням AC(включно) на XXX.

До прикладу , у мене є цей рядок (цей рядок тільки для тесту):

ssABteAstACABnnACss

і я хотів би висновок , подібний цьому: ssXXXABnnACss.


Я зробив це з perl:

$ echo 'ssABteAstACABnnACss' | perl -pe 's/AB.*?AC/XXX/'
ssXXXABnnACss

але я хочу це здійснити sed. Не працює наступне (за допомогою сумісного з виразками Perl):

$ echo 'ssABteAstACABnnACss' | sed -re 's/AB.*?AC/XXX/'
ssXXXss

2
Це не має сенсу. У вас є робоче рішення в Perl, але ви хочете використовувати Sed, чому?
Kusalananda

Відповіді:


16

Регекси седу відповідають найдовшій відповідності. Sed не має еквівалента не жадібного.

Очевидно, що ми хочемо зробити - це збіг

  1. AB,
    за яким
  2. будь-яка кількість нічого, крім того AC,
    за яким
  3. AC

На жаль, sedне можна зробити №2 - принаймні, не для регулярного виразу з кількома символами. Звичайно, для однозначного регулярного виразу, такого як @(або навіть [123]), ми можемо зробити [^@]*або [^123]*. І тому ми можемо обійти обмеження СЄПН шляхом змін всіх входжень ACв , @а потім в пошуках

  1. AB,
    за яким
  2. будь-яка кількість нічого, крім @,
    за яким
  3. @

подобається це:

sed 's/AC/@/g; s/AB[^@]*@/XXX/; s/@/AC/g'

Остання частина змінює неперевершені екземпляри @назад AC.

Але, звичайно, це необережний підхід, тому що вхідні дані вже можуть містити @символи, тому, зіставляючи їх, ми могли отримати помилкові позитиви. Однак, оскільки жодна змінна оболонки ніколи не матиме \x00символу NUL ( ), NUL, ймовірно, є хорошим символом, який буде використаний у вищезгаданій обробці замість @:

$ echo 'ssABteAstACABnnACss' | sed 's/AC/\x00/g; s/AB[^\x00]*\x00/XXX/; s/\x00/AC/g'
ssXXXABnnACss

Для використання NUL потрібен GNU sed. (Щоб переконатися, що функції GNU включені, користувач не повинен встановлювати змінну оболонки POSIXLY_CORRECT.)

Якщо ви використовуєте sed з -zпрапором GNU для обробки вхідного сигналу, розділеного NUL, наприклад, вихідного сигналу find ... -print0, то NUL не буде в просторі шаблонів, і NUL є хорошим вибором для заміни тут.

Хоча NUL не може бути в bash змінній, можливо включити його в printfкоманду. Якщо вхідний рядок може містити будь-який символ, включаючи NUL, то дивіться відповідь Стефана Шазеласа, який додає розумний метод виходу.


Я щойно відредагував вашу відповідь, щоб додати довге пояснення; сміливо підстригайте його або відкочуйте назад.
G-Man каже: "Відновіть Моніку"

@ G-Man Це відмінне пояснення! Дуже красиво зроблено. Дякую.
John1024

Ви можете echoабо printf`\ 000 'просто відмінно баш (або вхід може надходити з файлу). Але в цілому рядок тексту, звичайно, не має NUL.
ilkkachu

@ilkkachu Ви маєте рацію з цим. Що я повинен був написати, це те, що жодна змінна оболонка або параметр не може містити NUL. Відповідь оновлено.
John1024

Чи не буде це набагато безпечніше , якщо ви змінили , ACщоб AC@і назад?
Michael Vehrs

7

Деякі sedреалізації мають підтримку для цього. ssedмає режим PCRE:

ssed -R 's/AB.*?AC/XXX/g'

AT&T ast sed має сполучення та заперечення при використанні розширених регулярних виразів :

sed -A 's/AB(.*&(.*AC.*)!)AC/XXX/g'

Портативно, ви можете використовувати цю техніку: замініть кінцевий рядок (тут AC) одним символом, який не зустрічається ні в початковій, ні в кінці рядка (як :тут), щоб ви могли це зробити s/AB[^:]*://, і в тому випадку, якщо цей символ може з’явитися у вводі , використовуйте механізм пропуску, який не стикається з початковим і кінцевим рядками.

Приклад:

sed 's/_/_u/g; # use _ as the escape character, escape it
     s/:/_c/g; # escape our replacement character
     s/AC/:/g; # replace the end string
     s/AB[^:]*:/XXX/g; # actual replacement
     s/:/AC/g; # restore the remaining end strings
     s/_c/:/g; # revert escaping
     s/_u/_/g'

У GNU sedпідхід полягає у використанні нового рядка як символу заміни. Оскільки sedобробляє один рядок, новий рядок ніколи не виникає в просторі шаблону, тому можна:

sed 's/AC/\n/g;s/AB[^\n]*\n/XXX/g;s/\n/AC/g'

Це зазвичай не працює з іншими sedреалізаціями, оскільки вони не підтримують [^\n]. За допомогою GNU sedви повинні переконатися, що сумісність POSIX не ввімкнена (як, наприклад, зі змінною середовища POSIXLY_CORRECT).


6

Ні, регулярні регекси не мають жадної відповідності.

Ви можете зіставити весь текст до першого виникнення AC, використовуючи "все, що не містить AC", а далі - ACце те саме, що і Perl .*?AC. Справа в тому, що "все, що не містить AC", не може бути виражене легко як регулярний вираз: завжди є регулярний вираз, який розпізнає заперечення регулярного виразу, але регекс заперечення швидко ускладнюється. А в портативному sed це взагалі неможливо, тому що регулярний вираз заперечення вимагає групування чергування, яке присутнє в розширених регулярних виразах (наприклад, у awk), але не в портативних базових регулярних виразах. Деякі версії sed, такі як GNU sed, мають розширення BRE, які дозволяють виражати всі можливі регулярні вирази.

sed 's/AB\([^A]*\|A[^C]\)*A*AC/XXX/'

Через складність заперечення регулярного вираження це не є загальним. Що можна зробити замість цього - тимчасово перетворити лінію. У деяких реалізаціях sed можна використовувати нові рядки як маркер, оскільки вони не можуть відображатися у рядку введення (а якщо вам потрібно кілька маркерів, використовуйте новий рядок з наступним символом).

sed -e 's/AC/\
&/g' -e 's/AB[^\
]*\nAC/XXX/' -e 's/\n//g'

Однак майте на увазі, що зворотна косої лінії не працює в наборі символів з деякими версіями sed. Зокрема, це не працює у sed GNU, що є реалізацією sed у невбудованому Linux; в GNU sed ви можете використовувати \nзамість цього:

sed -e 's/AC/\
&/g' -e 's/AB[^\n]*\nAC/XXX/' -e 's/\n//g'

У цьому конкретному випадку достатньо замінити перший ACна новий рядок. Підхід, який я представив вище, більш загальний.

Більш потужним підходом у sed є збереження лінії у просторі утримування, видалення всіх, крім першої "цікавої" частини лінії, обмін простором утримування та простором шаблону або додавання простору шаблону до місця утримування та повторення. Однак якщо ви почнете робити такі складні речі, вам слід подумати над переходом на awk. У Awk немає і жадної відповідності, але ви можете розділити рядок і зберегти частини у змінних.


@ilkkachu Ні, це не так. s/\n//gвидаляє всі нові рядки.
Жил "ТАК - перестань бути злим"

asdf. Правильно, моє погано.
ilkkachu

3

sed - не жадібна відповідність Крістофа Зігарта

Трюк, щоб отримати не жадібну відповідність в sed, полягає в тому, щоб відповідати всім персонажам, за винятком того, який припиняє збіг. Я знаю, що не вимагає, але я витрачав на це дорогоцінні хвилини і сценарії оболонки, зрештою, повинен бути швидким і легким. Тож у випадку, якщо комусь це може знадобитися:

Жадібна відповідність

% echo "<b>foo</b>bar" | sed 's/<.*>//g'
bar

Не жадібна відповідність

% echo "<b>foo</b>bar" | sed 's/<[^>]*>//g'
foobar


3
Термін "безмозковий" неоднозначний. У цьому випадку не ясно, що ви (або Крістоф Зіґарт) це продумали. Зокрема, було б добре , якби ви показали , як вирішити цю проблему конкретної в питанні (де вираз нульовий через більш-of слідують по більш ніж один символ ) . Ви можете виявити, що в цьому випадку ця відповідь не працює добре.
Скотт

Кроляча нора набагато глибша, ніж мені здалося на перший погляд. Ви маєте рацію, що обхідне рішення не працює добре для регулярного вираження багато символів.
гресоліо

0

У вашому випадку ви можете просто відмовити закриття символу таким чином:

echo 'ssABteAstACABnnACss' | sed 's/AB[^C]*AC/XXX/'

2
Питання говорить: «Я хочу що - небудь між першим замінити ABі перша поява ACз XXX...» і дає в ssABteAstACABnnACssякості прикладу вхідних даних. Ця відповідь працює для цього прикладу , але взагалі не відповідає на питання. Наприклад, ssABteCstACABnnACssтакож слід отримати результат aaXXXABnnACss, але ваша команда передає цей рядок без змін.
G-Man каже: "Відновіть Моніку"

0

Рішення досить просте. .*жадібний, але не зовсім жадібний. Розглянемо відповідність ssABteAstACABnnACssпроти регулярного виразу AB.*AC. Далі, ACщо випливає, .*має насправді відповідати. Проблема полягає в тому, що через .*жадібність подальших ACбуде відповідати останнім, AC а не першим. .*з'їдає перший, в ACтой час як літерал ACв регулярному вираженні відповідає останньому в ssABteAstACABnn AC ss. Щоб цього не сталося, просто замініть перше ACна щось смішне, щоб відмежувати його від другого та від усього іншого.

echo ssABteAstACABnnACss | sed 's/AC/-foobar-/; s/AB.*-foobar-/XXX/'
ssXXXABnnACss

Жадібний .*тепер зупинитися біля підніжжя -foobar-в ssABteAst-foobar-ABnnACssтому , що немає іншого , -foobar-ніж це -foobar-, і регулярний вираз -foobar- повинні мати сірник. Попередня проблема полягала в тому, що у регулярному вираженні ACбуло два матчі, але, оскільки .*був жадібним, ACбув обраний останній матч для . Однак з -foobar-, можливий лише один матч, і цей матч доводить, що .*це не зовсім жадібно. Автобусна зупинка для цього .*відбувається, коли залишається лише одна відповідність для решти регенексів, що слідують далі .*.

Зауважте, що це рішення не вдасться, якщо воно ACз’явиться перед першим, ABоскільки неправильне ACбуде замінено на -foobar-. Наприклад, після першої sedзаміни ACssABteAstACABnnACssстає -foobar-ssABteAstACABnnACss; отже, проти не можна зустріти матч AB.*-foobar-. Однак якщо послідовність завжди ... AB ... AC ... AB ... AC ..., тоді це рішення матиме успіх.


0

Одна з альтернатив - змінити рядок, щоб ви хотіли жадібного збігу

echo "ssABtCeCAstACABnnACss" | rev | sed -E "s/(.*)CA.*BA(.*)/\1CA+-+-+-+-BA\2/" | rev

Використовуйте revдля обертання рядка, змінення критеріїв відповідності, використання sedзвичайного способу, а потім поверніть результат ....

ssAB-+-+-+-+ACABnnACss
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.