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

14

Eu quero fazer correspondência não padrão ganancioso (expressão regular) 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 desta longa string ?:

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

Eu quero obter este resultado:

 Author =   {Grzegorz J. Nalepa},



Eu tenho outro exemplo:

eco " , artigo {gjn, Autor = {Grzegorz J. Nalepa}," | awk '{sub (/ , [^,] *, /, ""); impressão }'
      ↑ ↑ ^^^^^

Observe que mudei os @caracteres para vírgula ( ,) na primeira posição da sequência de entrada e da expressão regular (e também mudei .*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
nowy1
fonte
4
Assim como o regex é inadequado para a análise robusta de HTML, eles provavelmente não serão capazes de fazer esse tipo de análise gramatical sensível ao contexto. No entanto, se o seu conjunto de entradas for bastante restrito e bem formado, você poderá se dar bem com a regex, desde que declare quais são suas restrições. Por exemplo, você pode procurar por Authorvírgula e espaço em branco, seguido por espaço em branco seguido por =espaço em branco seguido {por qualquer não- }seguido por }, embora isso exija (entre outras coisas) que você não possa aninhar {}dentro da = { ... }peça.
#
@ jw013, obrigado por sua explicação. No entanto, vou aguardar sugestões de outros usuários.
nowy1

Respostas:

18

Se você deseja selecionar @e até o primeiro ,depois disso, especifique-o como@[^,]*,

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

Essa abordagem funciona como o equivalente @.*?,, mas não para coisas como @.*?string, é aí que o que está depois é mais do que um único personagem. Negar um personagem é fácil, mas negar seqüências de caracteres em regexps é muito mais difícil .

Uma abordagem diferente é pré-processar sua entrada para substituir ou adicionar stringum caractere que, de outra forma, não ocorre em sua entrada:

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

Se você não pode garantir que a entrada não contenha seu caractere de substituição ( \1acima), uma abordagem é usar um mecanismo de escape:

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")

Isso funciona para strings fixos, mas não para regexps arbitrários, como para o equivalente a @.*?foo.bar.

Stéphane Chazelas
fonte
Muito obrigado pela boa resposta. Na minha edição, pedi mais um exemplo (veja minha edição).
nowy1
6

Já existem várias boas respostas que fornecem soluções awkalternativas para a incapacidade de fazer correspondências não gananciosas, por isso estou fornecendo algumas informações sobre uma maneira alternativa de fazê-lo usando PCRE ( Expressões Regulares Compatíveis com Perl ). Observe que a maioria dos awkscripts simples "combinar e imprimir" pode ser facilmente reimplementadaperl usando a -nopção de linha de comando, e scripts mais complexos podem ser convertidos com o conversor a2p Awk para Perl.

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

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.

De 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
cas
fonte
3

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

Existe uma maneira, reconhecidamente grosseira, de executar uma correspondência RE não gananciosa 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
}
Jim Mellander
fonte
2

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

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. smatchse comporta como match, retornando:

a posição em s que a expressão regular rocorre ou 0, se não ocorrer. As variáveis RSTARTe RLENGTHsão definidas para a posição e o comprimento da sequência correspondente.

ericbn
fonte
1

Não existe uma maneira no awk de fazer combinações não gananciosas. Você pode conseguir a saída desejada, no entanto. a sugestão de sch funcionará para essa linha. Se você não pode confiar em uma vírgula, mas "Autor" é sempre o início do que você deseja, você pode fazer o seguinte:

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

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

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

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

user17591
fonte
0

Há sempre uma maneira. O problema em questão 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, geralmente é necessário algo um pouco melhor. Nesse caso, encontrar palavras de parada geralmente compensa, pois 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}'
kerolasa
fonte
0

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

Saída
:, Autor = {Grzegorz J. Nalepa},

VINAY NAIR
fonte
1
Essa resposta está errada por cerca de cinco razões.
Scott
3
Você pode me ajudar a entender o que está errado? A saída parece consistente com o que é solicitado. Tentando entender por que a resposta está certa / não certa.
Vinay Nair