Як зменшити жадібність регулярного виразу в AWK?


14

Я хочу зробити не жадібний шаблон (регулярний вираз), що відповідає awk. Ось приклад:

echo "@article{gjn, Author =   {Grzegorz J. Nalepa}, " | awk '{ sub(/@.*,/,""); print }'

Чи можна записати регулярний вираз, який вибирає коротший рядок?

@article{gjn,

замість цієї довгої струни ?:

@article{gjn, Author =   {Grzegorz J. Nalepa},

Я хочу отримати такий результат:

 Author =   {Grzegorz J. Nalepa},



У мене є ще один приклад:

відлуння " , стаття {gjn, Author = {Grzegorz J. Nalepa}," | awk '{sub (/ , [^,] *, /, ""); друкувати} '
      ↑ ↑ ^^^^^

Зауважте, що я змінив @символи на символи кома ( ,) в першому положенні як вхідного рядка, так і регулярного виразу (а також змінив .*на [^,]*). Чи можна записати регулярний вираз, який вибирає коротший рядок?

, Author =   {Grzegorz J. Nalepa},

замість довшої струни ?:

,article{gjn, Author =   {Grzegorz J. Nalepa},

Я хочу отримати такий результат:

,article{gjn

4
Так само, як регулярні вирази є недостатніми для надійного розбору HTML, вони, ймовірно, не зможуть зробити такий граматичний граматичний аналіз. Однак якщо ваш набір входів досить обмежений і добре сформований, ви, можливо, зможете піти з регулярного вираження, поки ви заявите, які ваші обмеження. Наприклад, ви можете шукати Authorпробіл комами та пробілами, а потім пробіл, а =потім пробіл, за яким {слідує будь-який, без якого }слід }, хоча для цього потрібно (крім усього іншого), що ви не можете гніздоватись {}всередині = { ... }частини.
jw013

@ jw013, дякую за пояснення. І все ж я буду чекати пропозицій інших користувачів.
nowy1

Відповіді:


18

Якщо ви хочете вибрати @і до першого ,після цього, вам потрібно вказати його як@[^,]*,

Тобто @слід будь-якого кількість ( *) , які не є коми ( [^,]) з подальшою комою ( ,).

Такий підхід працює як еквівалент @.*?,, але не для таких речей @.*?string, тобто те, що далі, є більш ніж одним персонажем. Нехтувати персонажем нескладно, але заперечувати рядки в регулярних виразах набагато складніше .

Інший підхід полягає в попередній обробці вводу для заміни або додавання stringсимволу, який інакше не виникає у вашому введенні:

gsub(/string/, "\1&") # pre-process
gsub(/@[^\1]*\1string/, "")
gsub(/\1/, "") # revert the pre-processing

Якщо ви не можете гарантувати, що вхід не буде містити ваш символ заміни ( \1вище), одним із підходів є використання механізму пропуску:

gsub(/\1/, "\1\3") # use \1 as the escape character and escape itself as \1\3
                   # in case it's present in the input
gsub(/\2/, "\1\4") # use \2 as our maker character and escape it
                   # as \1\4 in case it's present in the input
gsub(/string/, "\2&") # mark the "string" occurrences

gsub(/@[^\2]*\2string/, "")

# then roll back the marking and escaping
gsub(/\2/, "")
gsub(/\1\4/, "\2")
gsub(/\1\3/, "\1")

Це працює для фіксованих strings, але не для довільних регулярних виразів, як для еквівалента @.*?foo.bar.


Дуже дякую за хорошу відповідь. Під час редагування я попросив ще один приклад (див. Мою редакцію).
nowy1

6

Уже є кілька хороших відповідей, що забезпечують awkнепрофесійну відповідність неможливим збігам, тому я надаю інформацію про альтернативний спосіб зробити це за допомогою Perl Compatible Regular Express (PCRE). Зауважте, що найпростіші awkсценарії "зіставити та надрукувати" можна легко повторно реалізувати за perlдопомогою параметра -nкомандного рядка, а складніші сценарії можна перетворити за допомогою перекладача a2p Awk to Perl.

Perl має не жадібний оператор, який можна використовувати в сценаріях Perl і в будь-якому, що використовує PCRE. Наприклад, також реалізований у -Pваріанті GNU grep .

PCRE не є ідентичним регулярним виразам Perl, але він дуже близький. Це популярний вибір бібліотеки регулярних виразів для багатьох програм, оскільки це дуже швидко, а розширення Perl до розширених регулярних виразів дуже корисні.

Із сторінки чоловіка perlre (1) :

   By default, a quantified subpattern is "greedy", that is, it will match
   as many times as possible (given a particular starting location) while
   still allowing the rest of the pattern to match.  If you want it to
   match the minimum number of times possible, follow the quantifier with
   a "?".  Note that the meanings don't change, just the "greediness":

       *?        Match 0 or more times, not greedily
       +?        Match 1 or more times, not greedily
       ??        Match 0 or 1 time, not greedily
       {n}?      Match exactly n times, not greedily (redundant)
       {n,}?     Match at least n times, not greedily
       {n,m}?    Match at least n but not more than m times, not greedily

3

Це стара публікація, але наступна інформація може бути корисною для інших.

Існує спосіб, правда, неодноразовий, щоб виконати не жадібне співпадіння RE в утилі. Основна ідея полягає у використанні функції match (string, RE) та поступово зменшувати розмір рядка, поки збіг не завершиться, щось на зразок (неперевірене):

if (match(string, RE)) {
    rstart = RSTART
    for (i=RLENGTH; i>=1; i--)
        if (!(match(substr(string,1,rstart+i-1), RE))) break;
    # At this point, the non-greedy match will start at rstart
    #  for a length of i+1
}

2

Для загальних виразів це може бути використано як негідний збіг:

function smatch(s, r) {
    if (match(s, r)) {
        m = RSTART
        do {
            n = RLENGTH
        } while (match(substr(s, m, n - 1), r))
        RSTART = m
        RLENGTH = n
        return RSTART
    } else return 0
}

Я використовую це на основі відповіді @ JimMellander. smatchповодиться так match, повертаючись:

позиція, в s якій відбувається регулярний вираз r, або 0, якщо цього немає. Змінні RSTARTі RLENGTHвстановлюються в положення і довжину відповідного рядка.


1

Немає способу в awk зробити не жадібну відповідність. Можливо, ви зможете отримати бажаний вихід. Пропозиція sch буде працювати для цього рядка. Якщо ви не можете покластися на кому, але "Автор" завжди є початком того, що ви хочете, ви можете зробити це:

awk '{ sub(/@.*Author/,"Author"); print }'

Якщо кількість символів, що передують Автору, завжди однакова, ви можете зробити це:

awk '{ sub(/@.{21}/,""); print }'

Вам просто потрібно знати, як виглядають ваші дані у всьому наборі.


0

Завжди є спосіб. Дану проблему можна вирішити досить легко, використовуючи коми як роздільник.

echo "@article{gjn2010jucs, Author =   {Grzegorz J. Nalepa}, " |
awk -F, '{sub(/^[ \t]/, "", $2); print $2}'

Коли кількість полів змінюється чимось краще, зазвичай потрібно. У такому випадку пошук слова зупинки часто окупається, оскільки ви можете вирізати що-небудь із рядка, використовуючи їх. У контексті прикладу ось що я маю на увазі під зупинками слів.

echo "@article{gjn2010jucs, Author =   {Grzegorz J. Nalepa}, " |
awk  '{sub(/.*Author/, "Author", $0); sub(/},.*/, "}", $0); print $0}'

0

Я знаю, що це стара публікація. Але ось щось просто використовується awk як OP як потрібно:
A = @ article {gjn2010jucs, Author = {Grzegorz J. Nalepa},
echo $ A | awk 'sub (/ @ [^,] * /, "")'

Вихід:,
Автор = {Grzegorz J. Nalepa},


1
Ця відповідь є неправильною з п'яти причин.
Скотт

3
Чи можете ви допомогти мені зрозуміти, що не так? Результат видається відповідним тому, що запитується. Намагаючись зрозуміти, чому відповідь правильна / неправильна.
VINAY NAIR
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.