Как уменьшить жадность регулярного выражения в 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},



У меня есть другой пример:

echo " , article {gjn, Author = {Grzegorz J. Nalepa}," | awk '{sub (/ , [^,] *, /, ""); Распечатать }'
      ↑ ↑ ^^^^^

Обратите внимание, что я изменил @символы на символы запятой ( ,) в первой позиции входной строки и регулярного выражения (а также изменился .*на [^,]*). Можно ли написать регулярное выражение, которое выбирает более короткую строку?

, Author =   {Grzegorz J. Nalepa},

вместо более длинной строки?

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

Я хочу получить этот результат:

,article{gjn
nowy1
источник
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-совместимых регулярных выражений (PCRE). Обратите внимание, что большинство простых awkсценариев «сопоставить и распечатать» можно легко повторно реализовать с perlпомощью параметра -nкомандной строки, а более сложные сценарии можно преобразовать с помощью переводчика a2p Awk в 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 в awk. Основная идея состоит в том, чтобы использовать функцию 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устанавливаются в положение и длину совпадающей строки.

ericbn
источник
1

В awk нет способа сделать не жадное сопоставление. Вы можете получить желаемый результат, хотя. Предложение Sch будет работать для этой линии. Если вы не можете полагаться на запятую, но «Автор» - это всегда начало того, что вы хотите, вы можете сделать это:

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

Если число символов, предшествующих автору, всегда одинаково, вы можете сделать это:

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

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

user17591
источник
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}'
kerolasa
источник
0

Я знаю, что это старый пост. Но вот что-то, использующее awk в качестве OP в соответствии с запросом:
A = @ article {gjn2010jucs, Author = {Grzegorz J. Nalepa},
echo $ A | awk 'sub (/ @ [^,] * /, "")'

Вывод:,
Author = {Grzegorz J. Nalepa},

ВИНАЙ НАИР
источник
1
Этот ответ неверен по пяти причинам.
Скотт
3
Можете ли вы помочь мне понять, что не так? Вывод, кажется, соответствует тому, что запрашивается. Попытка понять, почему ответ правильный / не правильный.
VINAY NAIR