Como reduzir a cobiça de uma expressão regular no AWK?

14

Eu quero fazer correspondência de padrão não-ganancioso (expressão regular) em awk . Aqui está um exemplo:

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

É possível escrever uma expressão regular que selecione a string mais curta?

@article{gjn,

em vez dessa longa string?:

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

Eu quero obter este resultado:

 Author =   {Grzegorz J. Nalepa},


Eu tenho outro exemplo:
echo ",article{gjn, Author =   {Grzegorz J. Nalepa}, " | awk '{ sub(/,[^,]*,/,""); print }'
      ↑                                                              ↑^^^^^

Observe que alterei os caracteres @ para caracteres de vírgula ( , ) na primeira posição da string de entrada e da expressão regular (e também alterou .* para [^,]* ). É possível escrever uma expressão regular que selecione a string mais curta?

, Author =   {Grzegorz J. Nalepa},

em vez da string mais longa?:

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

Eu quero obter este resultado:

,article{gjn
    
por nowy1 01.10.2012 / 18:34

7 respostas

17

Se você quiser selecionar @ e até o primeiro , depois disso, precisará especificá-lo como @[^,]*,

Isso é @ seguido por qualquer número ( * ) de não-vírgulas ( [^,] ) seguido por uma vírgula ( , ).

Essa abordagem funciona como o equivalente a @.*?, , mas não para coisas como @.*?string , que é onde o que vem depois é mais do que um único caractere. Negar um personagem é fácil, mas negar strings em regexps é muito mais difícil .

Uma abordagem diferente é pré-processar sua entrada para substituir ou preceder o string com um caractere que não ocorre em sua entrada:

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

Se você não puder garantir que a entrada não conterá seu caractere de substituição ( acima), uma abordagem é usar um mecanismo de escape:

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

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

# then roll back the marking and escaping
gsub(//, "")
gsub(//, "")
gsub(//, "")

Isso funciona para string s fixos, mas não para expressões regulares arbitrárias, como para o equivalente a @.*?foo.bar .

    
por 01.10.2012 / 20:06
6

Já existem várias boas respostas fornecendo soluções alternativas para a incapacidade de awk de fazer correspondências não gananciosas, então estou fornecendo algumas informações sobre uma maneira alternativa de fazer isso usando Perl Expressões regulares compatíveis (PCRE). Observe que os scripts " awk " de correspondência e impressão mais simples podem ser facilmente reimplementados em perl usando a opção de linha de comando -n , e scripts mais complexos podem ser convertidos com o a2p Tradutor Awk para Perl.

Perl tem um operador não-ganancioso que pode ser usado em scripts Perl e qualquer coisa que use PCRE. Por exemplo, também implementado na opção -P do GNU grep.

O PCRE é não idêntico às expressões regulares do Perl, mas é muito próximo. É uma escolha popular de uma biblioteca de expressões regulares para muitos programas, porque é muito rápida, e os aprimoramentos do Perl para expressões regulares estendidas são muito úteis.

Na página do manual 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
    
por 02.10.2012 / 00:16
2

Esta é uma postagem antiga, mas as informações a seguir podem ser úteis para outras pessoas.

Existe uma maneira, reconhecidamente grosseira, de executar correspondência não-gulosa de RE no awk. A idéia básica é usar a função match (string, RE) e reduzir progressivamente o tamanho da string até que a correspondência falhe, algo como (não testado):

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
}
    
por 25.08.2017 / 20:55
1

Não existe uma maneira no awk de fazer correspondência não-gananciosa. Você pode conseguir a saída desejada, no entanto. A sugestão de sch vai funcionar para essa linha. Se você não pode confiar em uma vírgula, mas "Autor" é sempre o começo do que você quer, você poderia fazer isso:

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

Se o número de caracteres que precede Autor for sempre o mesmo, você poderá fazer isso:

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

Você só precisa saber como são seus dados em todo o conjunto.

    
por 01.10.2012 / 20:29
1

Para expressões gerais, isso pode ser usado como correspondência não-gulosa:

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
}

Estou usando isso com base na resposta do @ JimMellander. smatch se comporta como match , retornando:

the position in s where the regular expression r occurs, or 0 if it does not. The variables RSTART and RLENGTH are set to the position and length of the matched string.

    
por 27.10.2017 / 14:00
-1

Existe sempre um jeito. O problema dado pode ser resolvido facilmente usando vírgulas como separador.

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

Quando o número de campos varia algo um pouco melhor geralmente é necessário. Nesse caso, encontrar palavras de parada geralmente vale a pena, já que você pode cortar qualquer coisa da linha usando-as. Dentro do contexto do exemplo, aqui está o que quero dizer com palavras de parada.

echo "@article{gjn2010jucs, Author =   {Grzegorz J. Nalepa}, " |
awk  '{sub(/.*Author/, "Author", $0); sub(/},.*/, "}", $0); print $0}'
    
por 22.05.2013 / 15:25
-1

Eu sei que este é um post antigo. Mas aqui está algo apenas usando o awk como OP conforme solicitado:
A = @ article {gjn2010jucs, autor = {Grzegorz J. Nalepa},
echo $ A | awk 'sub (/ @ [^,] * /, "")'

Resultado:
, Autor = {Grzegorz J. Nalepa},

    
por 08.06.2017 / 21:51