2012-04-08 18 views
71

¿Podría alguien decirme si debo o no incluir citas sobre las variables en un script de shell?Cuándo ajustar las comillas alrededor de una variable de shell?

Por ejemplo, es correcta la siguiente:

xdg-open $URL 
[ $? -eq 2 ] 

o

xdg-open "$URL" 
[ "$?" -eq "2" ] 

Y si es así, ¿por qué?

+10

No hay preguntas tontas. Bueno, hay _are, _ pero este no es uno de ellos :-) – paxdiablo

+1

Véase también http://unix.stackexchange.com/questions/171346/security-implications-of-forgetting-to-quote-a-variable- in-bash-posix-shells – tripleee

+0

Esta pregunta recibe muchos duplicados, muchos de los cuales no son sobre variables, así que retitulé a "valor" en lugar de "variable". Espero que esto ayude a más personas a encontrar este tema. – tripleee

Respuesta

45

Regla general: citar si puede estar vacío o contener espacios (o cualquier espacio en blanco realmente) o caracteres especiales (comodines). No citar cadenas con espacios a menudo conduce a que el caparazón rompa un único argumento en muchos.

$? no necesita cotizaciones ya que es un valor numérico. Si $URL lo necesita depende de lo que permita y si aún desea un argumento si está vacío.

Tiendo a decir siempre cadenas por hábito, ya que es más seguro de esa manera.

+0

para que alternase entre las variables que cotizan y las que no lo hacen en sus secuencias de comandos? gracias por su respuesta – Cristian

+0

Creo que vale la pena agregar a esta respuesta cuál sería el efecto de no citar una variable con espacios. – Owen

+0

¿solo tiene que citar variables de cadena? – Cristian

40

En resumen, cite todo lo que no requiera que el shell realice la división de tokens y la expansión de comodines.

Las comillas simples protegen el texto entre ellas al pie de la letra. Es la herramienta adecuada cuando necesitas asegurarte de que el caparazón no toque la cuerda en absoluto. Por lo general, es el mecanismo de cotización de elección cuando no requiere interpolación variable.

$ echo 'Nothing \t in here $will change' 
Nothing \t in here $will change 

$ grep -F '@&$*!!' file /dev/null 
file:I can't get this @&$*!! quoting right. 

Las comillas dobles son adecuadas cuando se requiere interpolación variable. Con las adaptaciones adecuadas, también es una buena solución cuando necesita comillas simples en la cadena. (No hay manera fácil de escapar de una comilla entre comillas simples, porque no hay ningún mecanismo de escape entre comillas simples - si había, ellos no citan textualmente por completo.)

$ echo "There is no place like '$HOME'" 
There is no place like '/home/me' 

sin comillas son adecuados cuando específicamente requiere que el intérprete de comandos realice la división de tokens y/o la expansión de comodines.

División de tokens;

$ words="foo bar baz" 
$ for word in $words; do 
> echo "$word" 
> done 
foo 
bar 
baz 

Por el contrario:

$ for word in "$words"; do echo "$word"; done 
foo bar baz 

(El bucle sólo se ejecuta una vez, a lo largo de la cadena única, citado.)

$ for word in '$words'; do echo "$word"; done 
$words 

(El bucle sólo se ejecuta una vez, más de lo literal sola -cinta citada.)

expansión comodín:

$ pattern='file*.txt' 
$ ls $pattern 
file1.txt  file_other.txt 

Por el contrario:

$ ls "$pattern" 
ls: cannot access file*.txt: No such file or directory 

(no hay un archivo llamado literalmente file*.txt.)

$ ls '$pattern' 
ls: cannot access $pattern: No such file or directory 

(no hay un archivo llamado $pattern, ya sea!)

En términos más concretos, cualquier cosa que contenga un nombre de archivo por lo general debe ser citado (porque los nombres de archivo pueden contener espacios en blanco y otros metacaracteres de shell). Cualquier cosa que contenga una URL generalmente se debe citar (porque muchas URL contienen metacaracteres del shell como ? y &). Cualquier cosa que contenga una expresión regular debería ser citada (ídem ídem). Se debe citar todo lo que contenga espacios en blanco significativos que no sean espacios únicos entre caracteres que no sean espacios en blanco (porque, de lo contrario, el shell afectará espacios en blanco, efectivamente, espacios únicos y recortará cualquier espacio en blanco inicial o posterior).

Cuando sabe que una variable solo puede contener un valor que no contiene metacaracteres del shell, las citas son opcionales. Por lo tanto, un $? sin comillas es básicamente correcto, ya que esta variable solo puede contener un solo número. Sin embargo, "$?" también es correcto y se recomienda para la coherencia general y la corrección (aunque esta es mi recomendación personal, no una política ampliamente reconocida).

Los valores que no son variables básicamente siguen las mismas reglas, aunque también podría escapar de cualquier metacaracteres en lugar de citarlos. Para un ejemplo común, una URL con un & en ella será analizado por la concha como un comando de fondo a menos que el metacarácter se escapó o citado:

$ wget http://example.com/q&uack 
[1] wget http://example.com/q 
-bash: uack: command not found 

(Por supuesto, esto también ocurre si la URL está en una variable sin comillas). Para una cadena estática, las comillas simples tienen más sentido, aunque cualquier forma de cita o escape funciona aquí.

wget 'http://example.com/q&uack' # Single quotes preferred for a static string 
wget "http://example.com/q&uack" # Double quotes work here, too (no $ or ` in the value) 
wget http://example.com/q\&uack # Backslash escape 
wget http://example.com/q'&'uack # Only the metacharacter really needs quoting 

El último ejemplo también sugiere otro concepto útil, que me gusta llamar "cesa de cotización". Si necesita mezclar comillas simples y dobles, puede usarlas adyacentes entre sí. Por ejemplo, las siguientes cadenas

'$HOME ' 
"isn't" 
' where `<3' 
"' is." 

citados se pueden pegar juntas espalda con espalda, formando una sola cadena larga después de tokenización y la cita de eliminación.

$ echo '$HOME '"isn't"' where `<3'"' is." 
$HOME isn't where `<3' is. 

Esto no es muy legible, pero es una técnica común y, por tanto, es bueno saberlo.

Como un lado, scripts should usually not use ls for anything. Para expandir un comodín, simplemente ... úselo.

$ printf '%s\n' $pattern # not ``ls -1 $pattern'' 
file1.txt 
file_other.txt 

$ for file in $pattern; do # definitely, definitely not ``for file in $(ls $pattern)'' 
> printf 'Found file: %s\n' "$file" 
> done 
Found file: file1.txt 
Found file: file_other.txt 

(El bucle es totalmente superflua en el último ejemplo;. printf trabaja específicamente bien con múltiples argumentos stat también Pero bucle durante un partido comodín es un problema común, y con frecuencia se hace incorrectamente..)

Una variable que contiene una lista de tokens para repetir o un comodín para expandirse se ve con menos frecuencia, por lo que a veces abreviamos para "citar todo a menos que sepa exactamente lo que está haciendo".

+0

Esta es una variante de (parte de) una respuesta que publiqué en una [pregunta relacionada] (http://stackoverflow.com/questions/25277037/printing-asterisk-in-bash-shell). Lo estoy pegando aquí porque es sucinto y está lo suficientemente definido como para convertirse en una pregunta canónica para este problema en particular. – tripleee

+0

Notaré que este es el elemento # 0 y un tema recurrente en la colección http://mywiki.wooledge.org/BashPitfalls de errores comunes de Bash. Muchos, muchos de los elementos individuales en esa lista son básicamente sobre este tema. – tripleee

4

Aquí es una fórmula de tres puntos para las cotizaciones en general:

comillas dobles

En contextos en los que queremos suprimir la división de palabras y uso de comodines. También en contextos en los que queremos que el literal sea tratado como una cadena, no como una expresión regular.

Las comillas simples

En literales de cadena en los que queremos suprimir la interpolación y tratamiento especial de las barras invertidas. En otras palabras, situaciones donde usar comillas dobles sería inapropiado.

No se cita

En contextos en los que estamos absolutamente seguros de que no hay división de palabras o temas englobamiento o que quieren la división de palabras y globbing.


Ejemplos

comillas dobles

  • cadenas literales con espacios en blanco ("StackOverflow rocks!", "Steve's Apple")
  • expansiones variables ("$var", "${arr[@]}")
  • sustituciones de comando ("$(ls)", "`ls`")
  • pegotes donde ruta de directorio o nombre de archivo de la pieza incluye espacios ("/my dir/"*)
  • para proteger comillas simples ("single'quote'delimited'string")
  • expansión de parámetros Bash ("${filename##*/}")

Las comillas simples

  • los nombres de comandos y argumentos que no tienen ningún espacio en blanco en ellos
  • cadenas literales que necesitan interpolación para ser suprimidas ('Really costs $$!', 'just a backslash followed by a t: \t')
  • para proteger comillas dobles ('The "crux"')
  • literales de expresiones regulares que necesitan interpolación para ser suprimidos
  • utilizar la cáscara citando para los literales que implican caracteres especiales ($'\n\t')
  • uso cáscara de citar en las que necesitamos para proteger varias cotizaciones individuales y dobles ($'{"table": "users", "where": "first_name"=\'Steve\'}')

No se cita

  • torno a las variables numéricas estándar ($$, $?, $# etc.)
  • en contextos aritméticos como ((count++)), "${arr[idx]}", "${string:start:length}"
  • dentro [[ ]] expresión que está libre de la división de palabras y temas englobamiento (esto es una cuestión de estilo y las opiniones pueden variar ampliamente)
  • donde queremos la división de palabras (for word in $words)
  • donde queremos globbing (for txtfile in *.txt; do ...)
  • donde queremos ~ debe interpretarse en el $HOME (~/"some dir" pero no "~/some dir")

Consulte también:

+3

De acuerdo con estas pautas, se obtendría una lista de archivos en el directorio raíz escribiendo '" ls ""/"' La frase "todos los contextos de cadena" debe calificarse con más cuidado. –

+4

En '[[]]', las comillas importan en el lado derecho de '='/'==' y '= ~': hace la diferencia entre interpretar una cadena como un patrón/expresión regular o literalmente. –

+0

@WilliamPursell: '" ls ""/"' es, de hecho, equivalente a 'ls /'. – mklement0

0

generalmente uso de citar como "$var" de seguro, a menos que esté seguro de que $var no contiene espacio.

hago uso $var como una forma sencilla de unir las líneas:

lines="`cat multi-lines-text-file.txt`" 
echo "$lines"        ## multiple lines 
echo $lines        ## all spaces (including newlines) are zapped 
Cuestiones relacionadas