¿Cuándo ajustar las comillas alrededor de una variable de shell?

¿Podría alguien decirme si debo o no incluir citas sobre las variables en un script de shell?

Por ejemplo, es el siguiente correcto:

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

o

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

Y si es así, ¿por qué?

Regla general: citarla si puede estar vacía 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 comillas ya que es un valor numérico. Si $URL necesita depende de lo que permita y si aún desea un argumento si está vacío.

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

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

Las comillas simples protegen el texto entre ellos textualmente. 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 una forma sencilla de escapar de una sola cita entre comillas simples, porque no hay un mecanismo de escape dentro de las comillas simples; si las hubiera, no citarían completamente al pie de la letra).

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

No son adecuadas las comillas cuando se requiere específicamente 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 ciclo solo se ejecuta una vez, sobre la única cadena entrecomillada).

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

(El ciclo solo se ejecuta una vez, sobre la cadena literal de una sola comilla).

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 ningún archivo llamado literalmente file*.txt )

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

(¡No hay ningún archivo llamado $pattern , tampoco!)

En términos más concretos, todo lo que contenga un nombre de archivo se debe citar normalmente (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 de 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 $? comillas $? está básicamente bien, porque 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, un shell con un & en él será analizado por el shell como un comando de fondo a menos que el metacarácter sea escapado o citado:

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

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

 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 “cotización de sube y baja”. Si necesita mezclar comillas simples y dobles, puede usarlas adyacentes entre sí. Por ejemplo, las siguientes cadenas citadas

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

se pueden pegar juntas una detrás de otra, formando una única cadena larga después de la tokenización y la eliminación de comillas.

 $ 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 lo tanto, es bueno saberlo.

Como un aparte, los scripts generalmente no deberían usar ls para nada. Para expandir un comodín, solo ... úsalo.

 $ 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 ciclo es completamente superfluo en el último ejemplo; printf específicamente funciona bien con múltiples argumentos. stat también. Pero el bucle sobre una coincidencia de 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".

Aquí hay una fórmula de tres puntos para citas en general:

Doble comillas

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

Comillas simples

En literales de cadenas donde queremos suprimir la interpolación y el tratamiento especial de las barras diagonales inversas. En otras palabras, situaciones donde usar comillas dobles sería inapropiado.

Sin comillas

En contextos en los que estamos absolutamente seguros de que no hay problemas de separación de palabras o globbing, o queremos división de palabras y globbing .


Ejemplos

Doble comillas

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

Comillas simples

  • nombres de comando y argumentos que tienen espacios en blanco en ellos
  • cadenas literales que necesitan que se suprima la interpolación ( 'Really costs $$!' , 'just a backslash followed by at: \t' )
  • para proteger las comillas dobles ( 'The "crux"' )
  • literales de expresiones regulares que necesitan que se suprima la interpolación
  • use comillas de shell para literales que involucren caracteres especiales ( $'\n\t' )
  • use comillas de shell donde necesitemos proteger varias comillas simples y dobles ( $'{"table": "users", "where": "first_name"=\'Steve\'}' )

Sin comillas

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

Ver también:

  • Diferencia entre comillas simples y dobles en Bash
  • ¿Cuáles son las variables especiales del shell del signo de dólar?

Generalmente uso citado como "$var" por seguridad, a menos que esté seguro de que $var no contiene espacio.

Yo uso $var como una forma simple de unir líneas:

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