¿Qué caracteres se deben escapar al usar Bash?

206

¿Hay alguna lista exhaustiva de caracteres que deben escaparse en Bash? ¿Se puede verificar solo con sed?

En particular, estaba comprobando si %es necesario escapar o no. Lo intenté

echo "h%h" | sed 's/%/i/g'

y funcionó bien, sin escapar %. ¿Significa que %no es necesario escapar? ¿Era esta una buena manera de verificar la necesidad?

Y más general: ¿son los mismos personajes para escapar shelly bash?

fedorqui 'así que deja de dañar'
fuente
44
En general, si te importa, lo estás haciendo mal. El manejo de datos nunca debe implicar ejecutarlos a través del proceso de análisis y evaluación utilizado para el código, haciendo que el escape sea discutible. Este es un paralelo muy cercano a las mejores prácticas para SQL, donde lo correcto es usar variables de enlace y lo incorrecto es tratar de "desinfectar" los datos inyectados a través de sustituciones de cadenas.
Charles Duffy
Relacionado con stackoverflow.com/questions/2854655/…
skywinder
8
@CharlesDuffy Sí, pero a veces lo que el motor de declaraciones preparado está haciendo en el backend es escapar de las cosas. ¿SO está "haciendo mal" porque escapan a los comentarios enviados por el usuario antes de mostrarlos en el navegador? No. Están previniendo XSS. No preocuparse en absoluto es hacerlo mal.
Parthian Shot
@ParthianShot, si el motor de declaraciones preparado no mantiene los datos completamente fuera de banda del código, las personas que lo escribieron deberían ser fusilados. Sí, sé que el protocolo de conexión de MySQL se implementa de esa manera; Mi declaración se mantiene.
Charles Duffy
@CharlesDuffy Y mi punto, que a veces sus opciones son hacer que algo funcione de manera segura usando una cadena de herramientas que haría que un purista se encoja, o se hunda ocho veces el tiempo y el esfuerzo para hacerlo bonito, también sigue en pie.
Parthian Shot

Respuestas:

282

Hay dos reglas fáciles y seguras que funcionan no solo en shsino también bash.

1. Ponga la cadena completa entre comillas simples

Esto funciona para todos los caracteres, excepto la comilla simple. Para escapar de la comilla simple, cierre la comilla anterior, inserte la comilla simple y vuelva a abrir la comilla.

'I'\''m a s@fe $tring which ends in newline
'

comando sed: sed -e "s/'/'\\\\''/g; 1s/^/'/; \$s/\$/'/"

2. Escapar de cada personaje con una barra invertida

Esto funciona para todos los personajes, excepto para la nueva línea. Para los caracteres de nueva línea, utilice comillas simples o dobles. Las cadenas vacías aún deben manejarse; reemplácelas por""

\I\'\m\ \a\ \s\@\f\e\ \$\t\r\i\n\g\ \w\h\i\c\h\ \e\n\d\s\ \i\n\ \n\e\w\l\i\n\e"
"

comando sed: sed -e 's/./\\&/g; 1{$s/^$/""/}; 1!s/^/"/; $!s/$/"/'.

2b. Versión más legible de 2

Hay un conjunto de caracteres fácil y seguro, como [a-zA-Z0-9,._+:@%/-], que se puede dejar sin escape para que sea más legible

I\'m\ a\ s@fe\ \$tring\ which\ ends\ in\ newline"
"

comando sed: LC_ALL=C sed -e 's/[^a-zA-Z0-9,._+@%/-]/\\&/g; 1{$s/^$/""/}; 1!s/^/"/; $!s/$/"/'.


Tenga en cuenta que en un programa sed, uno no puede saber si la última línea de entrada termina con un byte de nueva línea (excepto cuando está vacío). Es por eso que los dos comandos anteriores suponen que no. Puede agregar una nueva línea entre comillas manualmente.

Tenga en cuenta que las variables de shell solo se definen para el texto en el sentido POSIX. El procesamiento de datos binarios no está definido. Para las implementaciones que importan, el binario funciona con la excepción de los bytes NUL (porque las variables se implementan con cadenas C y están destinadas a usarse como cadenas C, es decir, argumentos de programa), pero debe cambiar a una configuración regional "binaria" como latin1 .


(Puede validar fácilmente las reglas leyendo la especificación POSIX para sh. Para bash, consulte el manual de referencia vinculado por @AustinPhillips)

Jo So
fuente
Nota: aquí se puede ver una buena variación del n. ° 1: github.com/scop/bash-completion/blob/… . No requiere ejecución sed, pero sí requiere bash.
jwd
44
Nota para cualquier otra persona (¡como yo!) Que lucha para que funcionen ... parece que el sabor de sed que obtienes en OSX no ejecuta estos comandos sed correctamente. ¡Sin embargo, funcionan bien en Linux!
dalelane
@dalelane: No puedo probar aquí. Edite cuando tenga una versión que funcione en ambos.
Jo So
Parece que te lo perdiste si la cadena comienza con un '-' (menos), ¿o eso solo se aplica a los nombres de archivo? - En este último caso necesitará un './' delante.
slashmais
No estoy seguro de lo que quieres decir. Con esos comandos sed, la cadena de entrada se toma de stdin.
Jo So
59

formato que se puede reutilizar como entrada de shell

Hay una directiva de formato especial printf ( %q) creada para este tipo de solicitud:

formato printf [-v var] [argumentos]

 %q     causes printf to output the corresponding argument
        in a format that can be reused as shell input.

Algunas muestras:

read foo
Hello world
printf "%q\n" "$foo"
Hello\ world

printf "%q\n" $'Hello world!\n'
$'Hello world!\n'

Esto también podría usarse a través de variables:

printf -v var "%q" "$foo
"
echo "$var"
$'Hello world\n'

Comprobación rápida con todos los (128) bytes ascii:

Tenga en cuenta que todos los bytes de 128 a 255 deben escaparse.

for i in {0..127} ;do
    printf -v var \\%o $i
    printf -v var $var
    printf -v res "%q" "$var"
    esc=E
    [ "$var" = "$res" ] && esc=-
    printf "%02X %s %-7s\n" $i $esc "$res"
done |
    column

Esto debe representar algo como:

00 E ''         1A E $'\032'    34 - 4          4E - N          68 - h      
01 E $'\001'    1B E $'\E'      35 - 5          4F - O          69 - i      
02 E $'\002'    1C E $'\034'    36 - 6          50 - P          6A - j      
03 E $'\003'    1D E $'\035'    37 - 7          51 - Q          6B - k      
04 E $'\004'    1E E $'\036'    38 - 8          52 - R          6C - l      
05 E $'\005'    1F E $'\037'    39 - 9          53 - S          6D - m      
06 E $'\006'    20 E \          3A - :          54 - T          6E - n      
07 E $'\a'      21 E \!         3B E \;         55 - U          6F - o      
08 E $'\b'      22 E \"         3C E \<         56 - V          70 - p      
09 E $'\t'      23 E \#         3D - =          57 - W          71 - q      
0A E $'\n'      24 E \$         3E E \>         58 - X          72 - r      
0B E $'\v'      25 - %          3F E \?         59 - Y          73 - s      
0C E $'\f'      26 E \&         40 - @          5A - Z          74 - t      
0D E $'\r'      27 E \'         41 - A          5B E \[         75 - u      
0E E $'\016'    28 E \(         42 - B          5C E \\         76 - v      
0F E $'\017'    29 E \)         43 - C          5D E \]         77 - w      
10 E $'\020'    2A E \*         44 - D          5E E \^         78 - x      
11 E $'\021'    2B - +          45 - E          5F - _          79 - y      
12 E $'\022'    2C E \,         46 - F          60 E \`         7A - z      
13 E $'\023'    2D - -          47 - G          61 - a          7B E \{     
14 E $'\024'    2E - .          48 - H          62 - b          7C E \|     
15 E $'\025'    2F - /          49 - I          63 - c          7D E \}     
16 E $'\026'    30 - 0          4A - J          64 - d          7E E \~     
17 E $'\027'    31 - 1          4B - K          65 - e          7F E $'\177'
18 E $'\030'    32 - 2          4C - L          66 - f      
19 E $'\031'    33 - 3          4D - M          67 - g      

Donde el primer campo es el valor hexadecimal del byte, el segundo contiene E si el carácter necesita ser escapado y el tercer campo muestra la presentación del carácter escapado.

Por qué , ?

Podrías ver algunos personajes que no siempre necesitan escapar, como ,, }y{ .

Entonces no siempre pero algún momento :

echo test 1, 2, 3 and 4,5.
test 1, 2, 3 and 4,5.

o

echo test { 1, 2, 3 }
test { 1, 2, 3 }

pero cuidado:

echo test{1,2,3}
test1 test2 test3

echo test\ {1,2,3}
test 1 test 2 test 3

echo test\ {\ 1,\ 2,\ 3\ }
test  1 test  2 test  3

echo test\ {\ 1\,\ 2,\ 3\ }
test  1, 2 test  3 
F. Hauri
fuente
Esto tiene el problema de que, al llamar a pritnf a través de bash / sh, primero se debe escapar la cadena para bash / sh
ThorSummoner
1
@ThorSummoner, no si pasa la cadena como argumento literal al shell desde un idioma diferente (donde presumiblemente ya sabe cómo citar). En Python: subprocess.Popen(['bash', '-c', 'printf "%q\0" "$@"', '_', arbitrary_string], stdin=subprocess.PIPE, stdout=subprocess.PIPE).communicate()le dará una versión correctamente citada de shell arbitrary_string.
Charles Duffy
1
FYI bash %qestuvo roto durante mucho tiempo: si mi mente me sirve bien, se solucionó un error (pero aún podría estar roto) en 2013 después de estar roto por ~ 10 años. Así que no confíes en ello.
Jo So
@CharlesDuffy Por supuesto, una vez que esté en Python land, shlex.quote()(> = 3.3, pipes.quote()- indocumentado - para versiones anteriores) también hará el trabajo y producirá una versión más legible para los humanos (agregando comillas y escapando, según sea necesario) de la mayoría de las cadenas, sin la necesidad de generar una concha.
Thomas Perl
1
Gracias por agregar notas especiales sobre ,. Me sorprendió saber que Bash incorporado printf -- %q ','da \,, pero /usr/bin/printf -- %q ','da ,(sin escapar). Lo mismo para otros caracteres: {, |, }, ~.
kevinarpe
34

Para salvar a alguien más de tener que usar RTFM ... en bash :

Encerrar caracteres entre comillas dobles preserva el valor literal de todos los caracteres dentro de las comillas, con la excepción de $, `, \, y, cuando se habilita la expansión de historia, !.

... así que si escapas de esos (y de la cita en sí misma, por supuesto) probablemente estés bien.

Si adopta un enfoque más conservador de 'en caso de duda, escape', debería ser posible evitar obtener caracteres con un significado especial al no escapar caracteres identificadores (es decir, letras ASCII, números o '_'). Es muy poco probable que estos (es decir, en algún extraño shell POSIX-ish) tengan un significado especial y, por lo tanto, necesiten escapar.

Mateo
fuente
1
aquí está el manual citado anteriormente: gnu.org/software/bash/manual/html_node/Double-Quotes.html
code_monk
Esta es una respuesta corta, dulce y en su mayoría correcta (+1 para eso), pero tal vez sea aún mejor usar comillas simples: vea mi respuesta más larga.
Jo So
26

Usando la print '%q' técnica , podemos ejecutar un ciclo para descubrir qué caracteres son especiales:

#!/bin/bash
special=$'`!@#$%^&*()-_+={}|[]\\;\':",.<>?/ '
for ((i=0; i < ${#special}; i++)); do
    char="${special:i:1}"
    printf -v q_char '%q' "$char"
    if [[ "$char" != "$q_char" ]]; then
        printf 'Yes - character %s needs to be escaped\n' "$char"
    else
        printf 'No - character %s does not need to be escaped\n' "$char"
    fi
done | sort

Da esta salida:

No, character % does not need to be escaped
No, character + does not need to be escaped
No, character - does not need to be escaped
No, character . does not need to be escaped
No, character / does not need to be escaped
No, character : does not need to be escaped
No, character = does not need to be escaped
No, character @ does not need to be escaped
No, character _ does not need to be escaped
Yes, character   needs to be escaped
Yes, character ! needs to be escaped
Yes, character " needs to be escaped
Yes, character # needs to be escaped
Yes, character $ needs to be escaped
Yes, character & needs to be escaped
Yes, character ' needs to be escaped
Yes, character ( needs to be escaped
Yes, character ) needs to be escaped
Yes, character * needs to be escaped
Yes, character , needs to be escaped
Yes, character ; needs to be escaped
Yes, character < needs to be escaped
Yes, character > needs to be escaped
Yes, character ? needs to be escaped
Yes, character [ needs to be escaped
Yes, character \ needs to be escaped
Yes, character ] needs to be escaped
Yes, character ^ needs to be escaped
Yes, character ` needs to be escaped
Yes, character { needs to be escaped
Yes, character | needs to be escaped
Yes, character } needs to be escaped

Algunos de los resultados, como ,parecen un poco sospechosos. Sería interesante obtener las aportaciones de @ CharlesDuffy sobre esto.

codeforester
fuente
2
Puede leer la respuesta para ,parecer un poco sospechoso en el último párrafo de mi respuesta
F. Hauri
2
Tenga en cuenta que %qno sabe en qué parte del shell está planeando usar el personaje, por lo que escapará a todos los personajes que pueden tener un significado especial en cualquier contexto de shell posible. ,en sí misma no tiene un significado especial para ella, pero como @ F.Hauri ha señalado en su respuesta, sí tiene un significado especial dentro de la {...}expansión de llaves : gnu.org/savannah-checkouts/gnu/bash/manual/… ¡ Esto es como! lo que también requiere expansión en situaciones específicas, no en general: echo Hello World!funciona bien, pero echo test!testfallará.
Mecki
18

Los caracteres que necesitan escapar son diferentes en Bourne o POSIX shell que Bash. Generalmente (muy) Bash es un superconjunto de esos proyectiles, por lo que todo lo que escapas shelldebe escapar en Bash.

Una buena regla general sería "en caso de duda, escapar de ella". Pero escapar de algunos personajes les da un significado especial, como \n. Estos se enumeran en las man bashpáginas debajo Quotingy echo.

Aparte de eso, escapa de cualquier personaje que no sea alfanumérico, es más seguro. No sé de una sola lista definitiva.

Las páginas del manual los enumeran a todos en alguna parte, pero no en un solo lugar. Aprende el idioma, esa es la manera de estar seguro.

Uno que me ha atrapado es !. Este es un personaje especial (expansión de la historia) en Bash (y csh) pero no en el shell Korn. Incluso echo "Hello world!"da problemas. El uso de comillas simples, como de costumbre, elimina el significado especial.

Cdarke
fuente
1
Me gusta especialmente el consejo Una buena regla general sería "en caso de duda, escapar" . Todavía tengo la duda de si consultar con sedes lo suficientemente bueno para ver si tiene que escapar. ¡Gracias por tu respuesta!
fedorqui 'SO deja de dañar'
2
@ fedorqui: sedNo es necesario consultar, puedes consultar con casi cualquier cosa. sedNo es el problema, bashes. Dentro de las comillas simples no hay caracteres especiales (excepto las comillas simples), ni siquiera puede escapar de los caracteres allí. Un sedcomando generalmente debe estar entre comillas simples porque los metacaracteres RE tienen demasiadas superposiciones con metacaracteres de shell para estar a salvo. La excepción es cuando se incrustan variables de shell, lo que debe hacerse con cuidado.
cdarke
55
Consulte con echo. Si saca lo que puso, no necesita escapar. :)
Mark Reed
6

Supongo que estás hablando de cuerdas de bash. Existen diferentes tipos de cadenas que tienen un conjunto diferente de requisitos para escapar. p.ej. Las cadenas de comillas simples son diferentes de las cadenas de comillas dobles.

La mejor referencia es el Citando sección del manual de bash.

Explica qué personajes necesitan escapar. Tenga en cuenta que algunos caracteres pueden necesitar escapar dependiendo de las opciones habilitadas, como la expansión del historial.

Austin Phillips
fuente
3
Por lo tanto, confirma que escapar es una jungla sin una solución fácil, tendrá que verificar cada caso. ¡Gracias!
fedorqui 'SO deja de dañar'
@fedorqui Como con cualquier idioma, hay un conjunto de reglas a seguir. Para el escape de cadenas bash, el conjunto de reglas es bastante pequeño como se describe en el manual. La cadena más fácil de usar son las comillas simples, ya que nada necesita escapar. Sin embargo, no hay forma de incluir una comilla simple en una sola cadena entre comillas.
Austin Phillips
@fedorqui. Es no una selva. Escapar es bastante factible. Mira mi nuevo post.
Jo So
@fedorqui No puede utilizar una comilla simple dentro de una cadena entre comillas simples pero puede "escapar" con algo como: 'texto' "'"' más texto '
CR.
4

Me di cuenta de que bash escapa automáticamente a algunos caracteres cuando usa el autocompletado.

Por ejemplo, si tiene un directorio llamado dir:A , bash se completará automáticamente paradir\:A

Con esto, realicé algunos experimentos con caracteres de la tabla ASCII y obtuve las siguientes listas:

Caracteres que bash escapa en autocompletar : (incluye espacio)

 !"$&'()*,:;<=>?@[\]^`{|}

Personajes que bash no escapa :

#%+-.0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ_abcdefghijklmnopqrstuvwxyz~

(Excluí /, ya que no se puede usar en los nombres de directorio)

yuri
fuente
2
Si realmente quisiera tener una lista completa, le sugiero que mire qué caracteres printf %qmodifica y qué no modifica si se pasa como argumento, idealmente, revisando todo el conjunto de caracteres.
Charles Duffy
Hay casos en los que incluso con la cadena de apóstrofo, es posible que desee escapar de letras y números para producir caracteres especiales. Por ejemplo: tr '\ n' '\ t' que traduce los caracteres de nueva línea en caracteres de tabulación.
Dick Guertin el