Número de barras invertidas necesarias para escapar de la barra diagonal inversa de expresiones regulares en la línea de comandos

12

Recientemente tuve problemas con algunas expresiones regulares en la línea de comandos, y descubrí que para hacer coincidir una barra invertida, se pueden usar diferentes números de caracteres. Este número depende de la cita utilizada para la expresión regular (ninguna, comillas simples, comillas dobles). Vea la siguiente sesión de bash para lo que quiero decir:

echo "#ab\\cd" > file
grep -E ab\cd file
grep -E ab\\cd file
grep -E ab\\\cd file
grep -E ab\\\\cd file
#ab\cd
grep -E ab\\\\\cd file
#ab\cd
grep -E ab\\\\\\cd file
#ab\cd
grep -E ab\\\\\\\cd file
#ab\cd
grep -E ab\\\\\\\\cd file
grep -E "ab\cd" file
grep -E "ab\\cd" file
grep -E "ab\\\cd" file
#ab\cd
grep -E "ab\\\\cd" file
#ab\cd
grep -E "ab\\\\\cd" file
#ab\cd
grep -E "ab\\\\\\cd" file
#ab\cd
grep -E "ab\\\\\\\cd" file
grep -E 'ab\cd' file
grep -E 'ab\\cd' file
#ab\cd
grep -E 'ab\\\cd' file
#ab\cd
grep -E 'ab\\\\cd' file

Esto significa que:

  • sin comillas, puedo hacer coincidir una barra invertida con 4-7 barras invertidas reales
  • con comillas dobles, puedo hacer coincidir una barra invertida con 3-6 barras invertidas reales
  • Con comillas simples, puedo hacer coincidir una barra invertida con 2-3 barras invertidas reales

Entiendo que el shell ignora una barra invertida adicional (desde la página de manual de bash):

"Una barra invertida no citada (\) es el carácter de escape. Conserva el valor literal del siguiente carácter que sigue"

Esto no se aplica a los ejemplos con comillas simples, porque no se realiza ningún escape entre comillas simples.

Y el comando grep ignora una barra invertida adicional ("\ c" es simplemente "c" escapado, pero esto es lo mismo que "c", porque "c" no tiene un significado especial en una expresión regular).

Esto explica el comportamiento del ejemplo con comillas simples, pero en realidad no entiendo los otros dos ejemplos, especialmente por qué hay una diferencia entre cadenas sin comillas y comillas dobles.

Una vez más, una cita de la página de manual de bash:

"El encerrar caracteres entre comillas dobles conserva el valor literal de todos los caracteres dentro de las comillas, con la excepción de $,`, \ y, cuando la expansión del historial está habilitada,! ".

Intenté lo mismo con GNU awk (por ejemplo awk /ab\cd/{print} file), con los mismos resultados.

Perl, sin embargo, muestra resultados diferentes (usando, por ejemplo perl -ne "/ab\\cd/"\&\&print file):

  • sin comillas, puedo hacer coincidir una barra invertida con 4-5 barras invertidas reales
  • con comillas dobles, puedo hacer coincidir una barra invertida con 3-4 barras invertidas reales
  • Con comillas simples, puedo combinar una barra invertida con 2 barras invertidas reales

¿Alguien puede explicar esa diferencia entre las cadenas de expresiones regulares no citadas y de doble letra en la línea de comandos para grep y awk? No estoy tan interesado en una explicación del comportamiento de Perl, ya que generalmente no uso las frases de Perl.

daniel kullmann
fuente

Respuestas:

10

Para el ejemplo no citado, cada \\par pasa una barra invertida a grep, por lo que 4 barras invertidas pasan dos a grep, lo que se traduce en una sola barra invertida. 6 barras invertidas pasan tres a grep, traduciéndose en una barra invertida y una \c, que es igual a c. Una barra invertida adicional no cambia nada, porque se traduce \c-> cpor el shell. Ocho barras invertidas en el shell son cuatro en grep, traducidas a dos, por lo que esto ya no coincide.

Para el ejemplo en comillas dobles, tenga en cuenta lo que sigue a su segunda cita de la página de manual de bash:

La barra diagonal inversa conserva su significado especial solo cuando le sigue uno de los siguientes caracteres: $, `,", \ o nueva línea.

Es decir, cuando da un número impar de barras invertidas, la secuencia termina en \c, lo que sería igual cen el caso sin comillas, pero cuando se cita, la barra invertida pierde su significado especial, por lo que \cse pasa a grep. Es por eso que el rango de barras invertidas "posibles" (es decir, las que componen un patrón que coincide con su archivo de ejemplo) se desliza hacia abajo en uno.

Ansgar Esztermann
fuente
... y luego hay algunas rarezas: por ejemplo: printf "\ntest"insertará una nueva línea antes de "prueba", aunque "\n"debería haber sido traducida "n"por el shell como está dentro de comillas dobles ... (entonces el resultado esperado debería ser, por "\ ntest", "ntest". Deberíamos tener el hábito de escribir: printf "\\ntest"o printf '\ntest', pero de alguna manera veo una gran cantidad de guiones que dependen de la rareza.
Olivier Dulac
6

Este enlace describe las citas de Bash y Escaping

Su pregunta trata sobre las primeras tres secciones.

  • Escape por personaje
  • Citas débiles "comillas dobles"
  • Citas fuertes 'comillas simples'
  • ANSI C como comillas
  • Cita I18N / L10N (Internacionalización y localización) .

A continuación se muestra una tabla de cómo las cadenas bashpasan grepy cómo greplas interpreta internamente.

Veamos primero echo "#ab\\cd" > file.
En las comillas débiles ("") "#ab\\cd", se \\trata de un escape \que se pasa filecomo un único literal \. Entonces, filecontiene ab\cd

Ahora, a sus comandos: El cuadro a continuación puede ayudar a ver qué sucede realmente con cada llamada. La *muestra los que coinciden con el contenido del archivo. Realmente es solo una cuestión de aplicar las reglas de escape de bash, como en la página web, con especial atención a la respuesta de daniel kullmann donde se refiere al comportamiento de escape en una situación de citas débiles .

La barra diagonal inversa conserva su significado especial solo cuando le sigue uno de los siguientes caracteres: $, `,", \ o nueva línea.


                            bash passes    grep further
                            to grep        resolves to         
grep -E ab\cd file            abcd           abcd   
grep -E ab\\cd file           ab\cd          abcd  
grep -E ab\\\cd file          ab\cd          abcd
grep -E ab\\\\cd file         ab\\cd         ab\cd    * 
grep -E ab\\\\\cd file        ab\\\cd        ab\cd    *
grep -E ab\\\\\\cd file       ab\\\cd        ab\cd    *    
grep -E ab\\\\\\\cd file      ab\\\cd        ab\cd    *
grep -E ab\\\\\\\\cd file     ab\\\\cd       ab\\cd

grep -E "ab\cd" file          ab\cd          abcd
grep -E "ab\\cd" file         ab\cd          abcd
grep -E "ab\\\cd" file        ab\\cd         ab\cd    *
grep -E "ab\\\\cd" file       ab\\cd         ab\cd    *
grep -E "ab\\\\\cd" file      ab\\\cd        ab\cd    *
grep -E "ab\\\\\\cd" file     ab\\\cd        ab\cd    *
grep -E "ab\\\\\\\cd" file    ab\\\\cd       ab\\cd    

grep -E 'ab\cd' file          ab\cd          abcd  
grep -E 'ab\\cd' file         ab\\cd         ab\cd    *
grep -E 'ab\\\cd' file        ab\\\cd        ab\cd    *
grep -E 'ab\\\\cd' file       ab\\\\cd       ab\\cd
Peter.O
fuente