¿Cómo reducir la codicia de una expresión regular en AWK?

14

Quiero hacer coincidir patrones no codiciosos (expresiones regulares) awk. Aquí hay un ejemplo:

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

¿Es posible escribir una expresión regular que seleccione la cadena más corta?

@article{gjn,

en lugar de esta larga cuerda ?:

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

Quiero obtener este resultado:

 Author =   {Grzegorz J. Nalepa},



Tengo otro ejemplo:

echo " , artículo {gjn, Autor = {Grzegorz J. Nalepa}," | awk '{sub (/ , [^,] *, /, ""); impresión }'
      ↑ ↑ ^^^^^

Tenga en cuenta que cambié los @caracteres a comas ( ,) en la primera posición de la cadena de entrada y la expresión regular (y también cambié .*a [^,]*). ¿Es posible escribir una expresión regular que seleccione la cadena más corta?

, Author =   {Grzegorz J. Nalepa},

en lugar de la cadena más larga ?:

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

Quiero obtener este resultado:

,article{gjn
nowy1
fuente
44
Así como las expresiones regulares son inadecuadas para un análisis HTML robusto, probablemente no podrán realizar este tipo de análisis gramatical sensible al contexto. Sin embargo, si su conjunto de entradas está bastante restringido y bien formado, es posible que pueda salirse con la expresión regular siempre que declare cuáles son sus restricciones. Por ejemplo, podría buscar Authorseguir una coma y un espacio en blanco, seguido de un espacio en blanco seguido de =un espacio en blanco seguido {de cualquier no }seguido }, aunque esto requiere (entre otras cosas) que no pueda anidar {}dentro de la = { ... }pieza.
jw013
@ jw013, gracias por tu explicación. Sin embargo, esperaré las sugerencias de otros usuarios.
nowy1

Respuestas:

18

Si desea seleccionar @y hasta el primero ,después de eso, debe especificarlo como@[^,]*,

Esto es @seguido por cualquier número ( *) de no comas ( [^,]) seguido de una coma ( ,).

Ese enfoque funciona como el equivalente de @.*?, , pero no para cosas como @.*?string, ahí es donde lo que está después es más que un solo personaje. Negar un personaje es fácil, pero negar cadenas en expresiones regulares es mucho más difícil .

Un enfoque diferente es preprocesar su entrada para reemplazar o anteponer el string con un carácter que de otra manera no ocurriría en su entrada:

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

Si no puede garantizar que la entrada no contendrá su carácter de reemplazo (\1 arriba), un enfoque es utilizar un 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")

Eso funciona para strings fijas pero no para expresiones regulares arbitrarias como para el equivalente de @.*?foo.bar.

Stéphane Chazelas
fuente
Muchas gracias por la buena respuesta. En mi edición, pedí otro ejemplo más (ver mi edición).
nowy1
6

Ya hay varias buenas respuestas que brindan soluciones para awkla incapacidad de hacer coincidencias no codiciosas, por lo que proporciono información sobre una forma alternativa de hacerlo utilizando Expresiones regulares compatibles con Perl (PCRE). Tenga en cuenta que las awksecuencias de comandos más simples de "coincidencia e impresión" se pueden volver a implementar fácilmente perlcon la -nopción de línea de comandos, y las secuencias de comandos más complejas se pueden convertir con traductor Awk to Perl de a2p .

Perl tiene un operador no codicioso que puede usarse en scripts de Perl y cualquier cosa que use PCRE. Por ejemplo, también implementado en la -Popción GNU grep .

PCRE no es idéntico a las expresiones regulares de Perl, pero está muy cerca. Es una opción popular de una biblioteca de expresiones regulares para muchos programas, porque es muy rápida y las mejoras de Perl para expresiones regulares extendidas son muy útiles.

Desde la página del comando man 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
fuente
3

Esta es una publicación antigua, pero la siguiente información puede ser útil para otros.

Hay una manera, ciertamente cruda, de realizar una correspondencia RE no codiciosa en awk. La idea básica es utilizar la función de coincidencia (cadena, RE) y reducir progresivamente el tamaño de la cadena hasta que la coincidencia falle, algo así como (no probado):

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
fuente
2

Para expresiones generales, esto se puede usar como una coincidencia no codiciosa:

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
}

Estoy usando esto basado en la respuesta de @ JimMellander. smatchse comporta como match, volviendo:

la posición en s la que se rproduce la expresión regular , o 0 si no es así. Las variables RSTARTy RLENGTHse establecen en la posición y longitud de la cadena coincidente.

ericbn
fuente
1

En awk no hay forma de hacer correspondencias no codiciosas. Sin embargo, es posible que pueda obtener la salida deseada. La sugerencia de sch funcionará para esa línea. Si no puede confiar en una coma, pero "Autor" es siempre el comienzo de lo que desea, puede hacer esto:

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

Si el número de caracteres que precede al Autor es siempre el mismo, puede hacer esto:

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

Solo necesita saber cómo se ven sus datos en todo el conjunto.

usuario17591
fuente
0

Siempre hay una manera. El problema dado se puede resolver con bastante facilidad utilizando comas como separador.

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

Cuando el número de campos varía, generalmente se necesita algo ligeramente mejor. En tal caso, encontrar palabras de detención a menudo vale la pena, ya que puede cortar cualquier cosa de la línea al usarlas. En el contexto del ejemplo, esto es lo que quiero decir con palabras vacías.

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

Sé que esta es una publicación anterior. Pero aquí hay algo simplemente usando awk como OP según lo solicitado:
A = @ article {gjn2010jucs, Author = {Grzegorz J. Nalepa},
echo $ A | awk 'sub (/ @ [^,] * /, "")'

Salida:
Autor = {Grzegorz J. Nalepa},

VINAY NAIR
fuente
1
Esa respuesta es incorrecta por cerca de cinco razones.
Scott
3
¿Puedes ayudarme a entender qué está mal? El resultado parece coherente con lo que se solicita. Tratando de entender por qué la respuesta es correcta / no correcta.
VINAY NAIR