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

184

¿Podría alguien decirme si debo ajustar o no las comillas alrededor de las variables en un script de shell?

Por ejemplo, es lo siguiente correcto:

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

o

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

Y si es así, ¿por qué?

Cristian
fuente
2
Consulte también unix.stackexchange.com/questions/171346/…
tripleee el
Esta pregunta recibe muchos duplicados, muchos de los cuales no son sobre variables, por lo que volví a "valor" en lugar de "variable". Espero que esto ayude a más personas a encontrar este tema.
tripleee
1
@codeforester ¿Qué pasa con la edición revertida?
tripleee
1
Relacionado: Diferencia entre comillas simples y dobles en Bash también.
codeforester

Respuestas:

131

Regla general: cítela 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 shell rompa un solo argumento en muchos.

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

Siempre tiendo a citar cadenas solo por costumbre, ya que es más seguro de esa manera.

paxdiablo
fuente
2
Tenga en cuenta que "espacios" realmente significa "cualquier espacio en blanco".
William Pursell
44
@Cristian: Si no está seguro de qué podría estar en la variable, es más seguro citarla. Tiendo a seguir el mismo principio que paxdiablo, y solo tengo la costumbre de citar todo (a menos que haya una razón específica para no hacerlo).
Gordon Davisson el
11
Si no conoce el valor de IFS, indíquelo sin importar qué. Si IFS=0, entonces echo $?puede ser muy sorprendente.
Charles Duffy
3
Cita basada en el contexto, no en lo que esperas que sean los valores, de lo contrario tus errores serán peores. Por ejemplo, está seguro de que ninguno de sus caminos tiene espacios, por lo que cree que puede escribir cp $source1 $source2 $dest, pero si por alguna razón inesperada destno se establece, el tercer argumento simplemente desaparece y se copiará source1en silencio en source2lugar de darle un error apropiado para el destino en blanco (como lo hubiera hecho si hubiera citado cada argumento).
Derek Veit
3
quote it if...tiene el proceso de pensamiento al revés: las citas no son algo que agrega cuando lo necesita, son algo que elimina cuando lo necesita. Siempre ajuste las cadenas y los scripts en comillas simples, a menos que necesite usar comillas dobles (por ejemplo, para permitir que una variable se expanda) o no necesite comillas (por ejemplo, para hacer globings y expandir nombres de archivos).
Ed Morton el
92

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

Las comillas simples protegen el texto entre ellas literalmente. Es la herramienta adecuada cuando necesita asegurarse de que el shell no toque la cadena en absoluto. Por lo general, es el mecanismo de cotización elegido cuando no se 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 adaptaciones adecuadas, también es una buena solución cuando necesita comillas simples en la cadena. (No hay una forma directa de escapar de una cita simple entre comillas simples, porque no hay un mecanismo de escape dentro de las comillas simples; si existiera, no citarían completamente al pie de la letra).

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

No hay comillas adecuadas cuando se requiere específicamente que el shell realice una división de tokens y / o 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 solo se ejecuta una vez, sobre la cadena única entre comillas).

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

(El ciclo solo se ejecuta una vez, sobre la cadena literal entre comillas simples).

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 $patterntampoco!)

En términos más concretos, cualquier cosa que contenga un nombre de archivo 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 debe citarse (porque muchas URL contienen metacaracteres de shell como ?y &). Cualquier cosa que contenga una expresión regular generalmente debe citarse (ídem ídem). Cualquier cosa que contenga espacios en blanco significativos que no sean espacios individuales entre caracteres que no sean espacios en blanco debe ser citado (porque de lo contrario, el shell combinará el espacio en blanco, efectivamente, en espacios individuales y recortará cualquier espacio en blanco inicial o final).

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

Los valores que no son variables básicamente siguen las mismas reglas, aunque también podría escapar a los metacaracteres en lugar de citarlos. Para un ejemplo común, &el shell analizará una URL con una entrada como un comando de fondo a menos que se escape o se cite el metacarácter:

$ 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 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 "cotización de balancín". 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, formando una sola cadena larga después de la tokenización y 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.

Por otro lado, los scripts generalmente no deberían usarse lspara 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 bucle es completamente superfluo en el último ejemplo; printfespecíficamente también funciona bien con múltiples argumentos stat. Pero recorrer un comodín es un problema común, y con frecuencia se hace incorrectamente).

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

tripleee
fuente
1
Esta es una variante de (parte de) una respuesta que publiqué en una pregunta relacionada . Lo estoy pegando aquí porque es lo suficientemente breve y bien definido como para convertirse en una pregunta canónica para este problema en particular.
tripleee
44
Notaré que este es el ítem # 0 y un tema recurrente en la colección 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
27

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 el globbing. También en contextos donde queremos que el literal sea tratado como una cadena, no como una expresión regular.

Comillas simples

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

Sin comillas

En contextos en los que estamos absolutamente seguros de que no hay problemas de división de palabras o problemas globales o si queremos dividir palabras y problemas globales .


Ejemplos

Doble comillas

  • cadenas literales con espacios en blanco ( "StackOverflow rocks!", "Steve's Apple")
  • expansiones 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
  • cadenas literales que necesitan interpolación para ser suprimidas ( 'Really costs $$!', 'just a backslash followed by a t: \t')
  • para proteger las comillas dobles ( 'The "crux"')
  • literales regex que necesitan interpolación para ser suprimidos
  • uso Shell citando para los literales que implican caracteres especiales ( $'\n\t')
  • use comillas de shell donde necesitamos proteger varias comillas simples y dobles ( $'{"table": "users", "where": "first_name"=\'Steve\'}')

Sin comillas

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

Ver también:

codeforester
fuente
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.
William Pursell
55
En [[ ]], la cita sí importa en el lado derecho de =/ ==y =~: hace la diferencia entre interpretar una cadena como un patrón / expresión regular o literalmente.
Benjamin W.
66
Una buena descripción general, pero vale la pena integrar los comentarios de @ BenjaminW. Las cadenas entre comillas ANSI C ( $'...') definitivamente deberían tener su propia sección.
mklement0
3
@ mklement0, de hecho, son equivalentes. Estas pautas indican que siempre debe escribir en "ls" "/"lugar de las más comunes ls /, y lo considero un defecto importante en las pautas.
William Pursell
44
Para sin comillas se puede agregar la asignación de variables o case:)
PesaThe
4

Por lo general, uso comillas como "$var"seguro, a menos que esté seguro de que $varno contiene espacio.

Lo uso $varcomo 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
Bach Lien
fuente
El comentario final es algo engañoso; las nuevas líneas se reemplazan efectivamente con espacios, no simplemente se eliminan.
tripleee
-1

Para usar las variables en el script de shell, utilice "" las variables citadas, ya que la citada significa que la variable puede contener espacios o caracteres especiales que no afectarán la ejecución de su script de shell. De lo contrario, si está seguro de no tener espacios o caracteres especiales en el nombre de su variable, puede usarlos sin "".

Ejemplo:

echo "$ url name" - (Se puede usar en todo momento)

echo "$ url name" - (No se puede usar en tales situaciones, así que tenga precaución antes de usarlo)

Vipul Sharma
fuente