Citar dentro de $ (sustitución de comando) en Bash

126

En mi entorno Bash, uso variables que contienen espacios, y uso estas variables dentro de la sustitución de comandos. Lamentablemente no puedo encontrar la respuesta en SE.

¿Cuál es la forma correcta de citar mis variables? ¿Y cómo debo hacerlo si están anidados?

DIRNAME=$(dirname "$FILE")

o cito fuera de la sustitución?

DIRNAME="$(dirname $FILE)"

¿o ambos?

DIRNAME="$(dirname "$FILE")"

o uso los ticks de vuelta?

DIRNAME=`dirname "$FILE"`

¿Cuál es la forma correcta de hacer esto? ¿Y cómo puedo verificar fácilmente si las comillas están correctas?

PrimoCocaína
fuente
1
Esta es una buena pregunta, pero dados todos los problemas con los espacios en blanco incrustados, ¿por qué te harías la vida difícil al usarlos a propósito?
Joe
3
@ Joe, con espacios en blanco incrustados, ¿quieres decir espacio en los nombres de archivo? Personalmente, no los uso con tanta frecuencia, pero estoy trabajando con directorios y archivos de otras personas de los que no estoy seguro si contienen espacios. Además, creo que es mejor hacerlo bien de inmediato para no tener que preocuparme en el futuro.
CousinCocaine
44
Si. ¿Qué vamos a hacer con esas "otras personas"? <G>
Joe

Respuestas:

188

En orden de peor a mejor:

  • DIRNAME="$(dirname $FILE)" no hará lo que quiera si $FILEcontiene espacios en blanco o caracteres de globo \[?*.
  • DIRNAME=`dirname "$FILE"`es técnicamente correcto, pero no se recomiendan los backticks para la expansión de comandos debido a la complejidad adicional al anidarlos.
  • DIRNAME=$(dirname "$FILE")es correcto, pero solo porque esta es una tarea . Si usa la sustitución de comandos en cualquier otro contexto, como export DIRNAME=$(dirname "$FILE")o du $(dirname "$FILE"), la falta de comillas causará problemas si el resultado de la expansión contiene espacios en blanco o caracteres globales.
  • DIRNAME="$(dirname "$FILE")"Es la forma recomendada. Puede reemplazar DIRNAME=con un comando y un espacio sin cambiar nada más, y dirnamerecibe la cadena correcta.

Para mejorar aún más:

  • DIRNAME="$(dirname -- "$FILE")"funciona si $FILEcomienza con un guión.
  • DIRNAME="$(dirname -- "$FILE"; printf x)" && DIRNAME="${DIRNAME%?x}"funciona incluso si $FILEtermina con una nueva línea, ya que $()corta las nuevas líneas al final de la salida y dirnamegenera una nueva línea después del resultado. Sheesh dirname, ¿por qué tienes que ser diferente?

Puede anidar expansiones de comandos tanto como desee. Con $()usted siempre cree un nuevo contexto de cotización, para que pueda hacer cosas como esta:

foo "$(bar "$(baz "$(ban "bla")")")"

Usted no quiere probar que con acentos abiertos.

l0b0
fuente
3
Respuesta clara a mi pregunta. Cuando anido estas variables, ¿puedo seguir citando como lo hago ahora?
CousinCocaine
3
¿Existe una referencia / recurso que detalle el comportamiento de las comillas dentro de una subposición de comando dentro de comillas?
AmadeusDrZaius
12
@AmadeusDrZaius " $()Siempre creas un nuevo contexto de citas", así que es como fuera de las comillas externas. No hay nada más, que yo sepa.
l0b0
44
@ l0b0 Gracias, sí, encontré su explicación muy clara. Me preguntaba si también estaba en un manual en alguna parte. Lo encontré (aunque no oficialmente) en wooledge . Supongo que si lees cuidadosamente sobre el orden de sustitución , podrías derivar este hecho como resultado.
AmadeusDrZaius
1
Por lo tanto, las citas anidadas son aceptables, pero nos desconciertan porque la mayoría de los esquemas de coloración de sintaxis no detectan la circunstancia especial. Ordenado.
Luke Davis el
19

Siempre puede mostrar los efectos de las comillas variables con printf.

División de palabras realizada en var1:

$ var1="hello     world"
$ printf '[%s]\n' $var1
[hello]
[world]

var1 citado, por lo que no hay división de palabras:

$ printf '[%s]\n' "$var1"
[hello     world]

Palabra dividida en el var1interior $(), equivalente a echo "hello" "world":

$ var2=$(echo $var1)
$ printf '[%s]\n' "$var2"
[hello world]

No hay división de palabras var1, no hay problema con no citar el $():

$ var2=$(echo "$var1")
$ printf '[%s]\n' "$var2"
[hello     world]

Palabra dividida de var1nuevo:

$ var2="$(echo $var1)"
$ printf '[%s]\n' "$var2"
[hello world]

Citando a ambos, la forma más fácil de estar seguro.

$ var2="$(echo "$var1")"
$ printf '[%s]\n' "$var2"
[hello     world]

Problema global

No citar una variable también puede conducir a una expansión global de su contenido:

$ mkdir test; cd test; touch file1 file2
$ var="*"
$ printf '[%s]\n' $var
[file1]
[file2]
$ printf '[%s]\n' "$var"
[*]

Tenga en cuenta que esto sucede después de que la variable se expande solo. No es necesario citar un glob durante la asignación:

$ var=*
$ printf '[%s]\n' $var
[file1]
[file2]
$ printf '[%s]\n' "$var"
[*]

Use set -fpara deshabilitar este comportamiento:

$ set -f
$ var=*
$ printf '[%s]\n' $var
[*]

Y set +fpara volver a habilitarlo:

$ set +f
$ printf '[%s]\n' $var
[file1]
[file2]
Graeme
fuente
8
La gente tiende a olvidar que la división de palabras no es el único problema, es posible que desee cambiar su ejemplo para tener var1='hello * world'que ilustrar también el problema global.
Stéphane Chazelas
10

Adición a la respuesta aceptada:

Aunque generalmente estoy de acuerdo con la respuesta de @ l0b0 aquí, sospecho que la colocación de backticks desnudos en la lista de "peor a mejor" es al menos en parte el resultado de la suposición de que $(...)está disponible en todas partes. Me doy cuenta de que la pregunta especifica Bash, pero hay muchas ocasiones en las que Bash /bin/shparece significar , lo que puede no ser siempre el shell Bourne Again completo.

En particular, el shell Bourne simple no sabrá qué hacer $(...), por lo que los scripts que afirman ser compatibles con él (por ejemplo, a través de una #!/bin/shlínea shebang) probablemente se comportarán mal si en realidad son ejecutados por lo "real" /bin/sh. interés especial cuando, por ejemplo, produce scripts de inicio o empaqueta pre y post scripts, y puede colocar uno en un lugar sorprendente durante la instalación de un sistema base.

Si algo de eso suena como algo que está planeando hacer con esta variable, el anidamiento probablemente sea menos preocupante que tener el script realmente ejecutado de manera predecible. Cuando es un caso bastante simple y la portabilidad es una preocupación, incluso si espero que el script generalmente se ejecute en sistemas donde /bin/shestá Bash, a menudo tiendo a usar backticks por esta razón, con múltiples asignaciones en lugar de anidar.

Habiendo dicho todo eso, la ubicuidad de las conchas que implementan $(...)(Bash, Dash, et al.), Nos deja en un buen lugar para seguir con la sintaxis POSIX más bonita, más fácil de anidar y más recientemente preferida en la mayoría de los casos, para todas las razones que @ l0b0 menciona.

Aparte: esto también ha aparecido ocasionalmente en StackOverflow -

rsandwick3
fuente
//, Excelente respuesta. Me he encontrado con problemas de compatibilidad con / bin / sh en el pasado también. ¿Tienes algún consejo sobre cómo lidiar con su problema utilizando métodos compatibles con versiones anteriores?
Nathan Basanese
en mi experiencia: a veces es posible que necesites cambiar los backticks por un par de dólares encerrados, y ni una sola vez ha ayudado (ha sido necesario, para ser específico) a cambiar los paren de dólares incluidos en los backticks. Solo escribo backticks en mi código javascript y no en el código shell.
Steven Lu