¿Cómo o por qué usar `. *?` Es mejor que `. *`?

9

Respondí esta pregunta en SuperUser que estaba relacionada con el tipo de expresiones regulares que se usaban al agrupar una salida.

La respuesta que di fue esta:

 tail -f log | grep "some_string.*some_string"

Y luego, en tres comentarios a mi respuesta, @Bob escribió esto:

.*es codicioso y puede capturar más de lo que quieres. .*?Suele ser mejor.

Luego esto,

el ?es un modificador activado *, lo que lo hace perezoso en lugar del codicioso predeterminado. Asumiendo PCRE.

Busqué en Google PCRE, pero no pude entender cuál es el significado de esto en mi respuesta.

y finalmente esto,

También debo señalar que esto es regex (grep haciendo POSIX regex por defecto), no un shell glob.

Solo sé qué es un Regex y su uso muy básico en el comando grep. Entonces, no pude obtener ninguno de esos 3 comentarios y tengo estas preguntas en mente:

  • ¿Cuáles son las diferencias en el uso de .*?frente .*?
  • ¿Cuál es mejor y bajo qué circunstancia? Por favor proporcione ejemplos.

También sería útil entender los comentarios, si alguien pudiera


ACTUALIZACIÓN: Como respuesta a la pregunta ¿En qué se diferencia Regex de Shell Globs? @Kusalananda proporcionó este enlace en su comentario.

NOTA: Si es necesario, lea mi respuesta a esta pregunta antes de responder para consultar el contexto.

C0deDaedalus
fuente
Estas son dos preguntas muy diferentes. La primera pregunta es respondida por unix.stackexchange.com/questions/57957/… mientras que la segunda pregunta depende de la aplicación del patrón (no se puede decir que sea "mejor" en todas las circunstancias).
Kusalananda
Puede editar esta pregunta para que sea solo sobre el tema .*vs. .*?La pregunta "diferencia entre expresiones regulares y globos de shell" ya se ha abordado en este sitio.
Kusalananda

Respuestas:

7

Ashok ya señaló la diferencia entre .*y .*?, así que solo proporcionaré información adicional.

grep (suponiendo que la versión GNU) admite 4 formas de unir cadenas:

  • Cuerdas fijas
  • Expresiones regulares básicas (BRE)
  • Expresiones regulares extendidas (ERE)
  • Expresiones regulares compatibles con Perl (PCRE)

grep usa BRE por defecto.

BRE y ERE están documentados en el capítulo Expresiones regulares de POSIX y PCRE está documentado en su sitio web oficial . Tenga en cuenta que las características y la sintaxis pueden variar entre las implementaciones.

Vale la pena decir que ni BRE ni ERE admiten la pereza :

El comportamiento de múltiples símbolos de duplicación adyacentes ('+', '*', '?' E intervalos) produce resultados indefinidos.

Entonces, si desea usar esa función, deberá usar PCRE en su lugar:

# BRE greedy
$ grep -o 'c.*s' <<< 'can cats eat plants?'
can cats eat plants

# BRE lazy
$ grep -o 'c.*\?s' <<< 'can cats eat plants?'
can cats eat plants

# ERE greedy
$ grep -E -o 'c.*s' <<< 'can cats eat plants?'
can cats eat plants

# ERE lazy
$ grep -E -o 'c.*?s' <<< 'can cats eat plants?'
can cats eat plants

# PCRE greedy
$ grep -P -o 'c.*s' <<< 'can cats eat plants?'
can cats eat plants

# PCRE lazy
$ grep -P -o 'c.*?s' <<< 'can cats eat plants?'
can cats

Editar 1

¿Podría explicar un poco sobre .*vs .*??

  • .*se usa para hacer coincidir el patrón "más largo" 1 posible.

  • .*?se utiliza para hacer coincidir el patrón "más corto" 1 posible.

En mi experiencia, el comportamiento más buscado suele ser el segundo.

Por ejemplo, supongamos que tenemos la siguiente cadena y solo queremos hacer coincidir las etiquetas html 2 , no el contenido entre ellas:

<title>My webpage title</title>

Ahora compara .*vs .*?:

# Greedy
$ grep -P -o '<.*>' <<< '<title>My webpage title</title>'
<title>My webpage title</title>

# Lazy
$ grep -P -o '<.*?>' <<< '<title>My webpage title</title>'
<title>
</title>

1. El significado de "más largo" y "más corto" en un contexto de expresiones regulares es un poco complicado, como señaló Kusalananda . Consulte la documentación oficial para más información.
2. No se recomienda analizar html con regex . Este es solo un ejemplo con fines educativos, no lo use en la producción.

nxnev
fuente
¿Podría explicar un poco sobre .*vs .*??
C0deDaedalus
@ C0deDaedalus Actualizado.
nxnev
9

Supongamos que tomo una cadena como:

can cats eat plants?

El uso de codicioso c.*scoincidirá con toda la cadena, ya que comienza cy termina con s, siendo un operador codicioso, continúa coincidiendo hasta la aparición final de s.

Mientras que usar el vago c.*?ssolo coincidirá hasta que se encuentre la primera aparición de s, es decir, cadena can cats.

A partir del ejemplo anterior, es posible que pueda reunir eso:

"Codicioso" significa hacer coincidir la cadena más larga posible. "Perezoso" significa hacer coincidir la cadena más corta posible. Adición de una ?a un cuantificador como *, +, ?, o {n,m}hace que sea perezoso.

Ashok
fuente
1
Sería "lo más corto posible" cats, por lo que no se aplica estrictamente "lo más corto posible" en ese sentido.
Kusalananda
2
@Kusalananda es cierto, no estrictamente en ese sentido pero "lo más corto posible" aquí significa entre la primera aparición de c y s.
Ashok
1

Una cadena podría coincidir de varias maneras (de simple a más compleja):

  1. Como una cadena estática (Suponga var = '¡Hola Mundo!'):

    [ "$var" = "Hello World!" ] && echo yes
    echo "$var" | grep -F "Hello"
    grep -F "Hello" <<<"$var"

  2. Como un pegote:

    echo ./* # enumera todos los archivos en pwd.
    case $var in (*Worl*) echo yes;; (*) echo no;; esac
    [[ "$var" == *"Worl"* ]] && echo yes

    Hay globos básicos y extendidos. El caseejemplo usa globos básicos. El [[ejemplo bash usa globos extendidos. La primera coincidencia de archivo podría ser básica o extendida en algún shell como la configuración extgloben bash. Ambos son idénticos en este caso. Grep no podía usar globos.

    El asterisco en un globo significa algo diferente a un asterisco en una expresión regular :

    * matches any number (including none) ofcualquier personaje .
    * matches any number (including none) of theelemento anterior .

  3. Como una expresión regular básica (BRE):

    echo "$var" | sed 's/W.*d//' # print: ¡Hola!
    grep -o 'W.*d' <<<"$var" # print World!

    No hay BRE en shells (básicos) o awk.

  4. Expresiones regulares extendidas (ERE):

    [[ "$var" =~ (H.*l) ]] # match: Hola Worl
    echo "$var" | sed -E 's/(d|o)//g' # print: Hell Wrl!
    awk '/W.*d/{print $1}' <<<"$var" # print: Hola
    grep -oE 'H.*l' <<<"$var" # print: Hola Worl

  5. Expresiones regulares compatibles con Perl:

    grep -oP 'H.*?l # print: Hel

Solo en un PCRE a *?tiene algún significado de sintaxis específica.
Hace que el asterisco sea perezoso (sin gracia): pereza en lugar de avaricia .

$ grep -oP 'e.*l' <<<"$var"
ello Worl

$ grep -oP 'e.*?l' <<<"$var"
el

Esto es solo la punta del iceberg, hay codiciosos, perezosos , dóciles o posesivos . También hay mirar hacia adelante y hacia atrás, pero no se aplican al asterisco *.

Hay una alternativa para obtener el mismo efecto que una expresión regular no codiciosa:

$ grep -o 'e[^o]*o' <<<"$var"
ello

La idea es muy simple: no use un punto ., niegue el siguiente carácter para que coincida [^o]. Con una etiqueta web:

$ grep -o '<[^>]*>' <<<'<script type="text/javascript">document.write(5 + 6);</script>'
<script type="text/javascript">
</script>

Lo anterior debería aclarar completamente todos los comentarios de @Bob 3. Parafraseando:

  • A. * Es una expresión regular común, no un globo.
  • Solo una expresión regular podría ser compatible con PCRE.
  • En PCRE: a? modificar el cuantificador * .*es codicioso .*?no lo es.

Preguntas

  • ¿Cuáles son las diferencias en el uso de. ? vs. ?

    • A .*?es válido solo en sintaxis PCRE.
    • A .*es más portátil.
    • El mismo efecto que una coincidencia no codiciosa podría lograrse reemplazando el punto con un rango de caracteres negado: [^a]*
  • ¿Cuál es mejor y bajo qué circunstancia? Por favor proporcione ejemplos.
    ¿Mejor? Depende de la meta. No hay mejor, cada uno es útil para diferentes propósitos. He proporcionado varios ejemplos arriba. ¿Necesitas más?

Isaac
fuente