¿Por qué la expansión de parámetros con espacios sin comillas funciona dentro de los corchetes dobles "[[" pero no dentro de los corchetes simples "["?

86

Estoy confundido con el uso de corchetes simples o dobles. Mira este código:

dir="/home/mazimi/VirtualBox VMs"

if [[ -d ${dir} ]]; then
    echo "yep"
fi

Funciona perfectamente aunque la cadena contiene un espacio. Pero cuando lo cambio a un solo soporte:

dir="/home/mazimi/VirtualBox VMs"

if [ -d ${dir} ]; then
    echo "yep"
fi

Dice:

./script.sh: line 5: [: /home/mazimi/VirtualBox: binary operator expected

Cuando lo cambio a:

dir="/home/mazimi/VirtualBox VMs"

if [ -d "${dir}" ]; then
    echo "yep"
fi

Funciona bien. ¿Alguien puede explicar lo que está pasando? ¿Cuándo debo asignar comillas dobles alrededor de variables como "${var}"para evitar problemas causados ​​por espacios?

Majid Azimi
fuente
8
Bash FAQ 31
jw013
Una pregunta más genérica vs: unix.stackexchange.com/questions/306111/…
Ciro Santilli 新疆 改造 中心 法轮功 六四 事件

Respuestas:

83

El único soporte [es en realidad un alias para el testcomando, es no sintaxis.

Una de las desventajas (de muchos) del paréntesis simple es que si uno o más de los operandos que está tratando de evaluar devuelven una cadena vacía, se quejará de que esperaba dos operandos (binario). Es por eso que ves que la gente lo hace [ x$foo = x$blah ], las xgarantías de que el operando nunca se evaluará en una cadena vacía.

El doble soporte [[ ]], por otro lado, es sintaxis y es mucho más capaz que [ ]. Como descubrió, no tiene el problema de un solo operando y también permite más sintaxis tipo C con los >, <, >=, <=, !=, ==, &&, ||operadores.

Mi recomendación es la siguiente: si su intérprete es #!/bin/bash, entonces siempre use[[ ]]

Es importante señalar que [[ ]]no es compatible con todas las conchas POSIX, sin embargo muchas conchas que apoyan tales como zshy ksh, además debash

SiegeX
fuente
16
El problema de la cadena vacía (y muchos otros) se resuelve mediante comillas. La "x" es para cubrir otro tipo de problemas: donde los operandos pueden tomarse como operadores . Como cuando $fooes !o (o -n... Ese problema no está destinado a ser un problema con shells POSIX donde el número de argumentos (al lado [y ]) no es mayor que cuatro.
Stéphane Chazelas
21
Para aclarar, [ x$foo = x$blah ]es tan incorrecto como [ $foo = $bar ]. [ "$foo" = "$bar" ]es correcto en cualquier shell compatible con POSIX, [ "x$foo" = "x$bar" ]funcionaría en cualquier shell similar a Bourne, pero xno es para los casos en que tiene cadenas vacías, sino dónde $foopuede estar !o -n...
Stéphane Chazelas
1
Semántica interesante en esta respuesta. No estoy seguro de lo que quieres decir con su sintaxis * no * . Por supuesto, [no es precisamente un alias para test. Si lo fuera, entonces testaceptaría un corchete de cierre. test -n foo ]. Pero testno lo hace, y [requiere uno. [es idéntico a testtodos los demás aspectos, pero no es así como aliasfunciona. Bash describe [y testcomo shell incorporado , pero [[como una palabra clave de shell .
kojiro
1
Bueno, en bash [es un shell incorporado, pero / usr / bin / [también es un ejecutable. Tradicionalmente es un enlace a / usr / bin / test, pero en gnu coreutils moderno es un binario separado. La versión tradicional de prueba examina argv [0] para ver si se invoca como [y luego busca la coincidencia].
Evan
Mi fiesta no funciona con >=y<=
Estudiante
52

El [comando es un comando ordinario. Aunque la mayoría de los shells lo proporcionan como una eficiencia incorporada, obedece las reglas sintácticas normales del shell. [es exactamente equivalente a test, excepto que [requiere un ]como último argumento y testno lo hace.

Los corchetes dobles [[ … ]]son sintaxis especial. Se introdujeron en ksh (varios años después [) porque [puede ser problemático de usar correctamente y [[permite algunas nuevas adiciones agradables que usan caracteres especiales de shell. Por ejemplo, puedes escribir

[[ $x = foo && $y = bar ]]

porque toda la expresión condicional es analizada por el shell, mientras [ $x = foo && $y = bar ]que primero se dividiría en dos comandos [ $x = fooy se $y = bar ]separaría por el &&operador. Del mismo modo, los corchetes dobles permiten cosas como la sintaxis de coincidencia de patrones, por ejemplo, [[ $x == a* ]]para probar si el valor de xcomienza con a; entre paréntesis, esto se expandiría a*a la lista de archivos cuyos nombres comienzan aen el directorio actual. Los corchetes dobles se introdujeron por primera vez en ksh y solo están disponibles en ksh, bash y zsh.

Dentro de los corchetes simples, debe usar comillas dobles alrededor de sustituciones variables, como en la mayoría de los otros lugares, porque son solo argumentos para un comando (que resulta ser el [comando). Dentro de los corchetes dobles, no necesita comillas dobles, ya que el shell no divide ni engloba palabras: analiza una expresión condicional, no un comando.

Sin embargo, una excepción es [[ $var1 = "$var2" ]]cuando necesita las comillas si desea hacer una comparación de cadena byte a byte, de lo contrario, $var2sería un patrón para comparar $var1.

Una cosa que no puede hacer [[ … ]]es usar una variable como operador. Por ejemplo, esto es perfectamente legal (pero rara vez útil):

if [ -n "$reverse_sort" ]; then op=-gt; else op=-lt; fi

if [ "$x" "$op" "$y" ]; then 

En tu ejemplo

dir="/home/mazimi/VirtualBox VMs"
if [ -d ${dir} ]; then 

el comando dentro de la ifes [con los 4 argumentos -d, /home/mazimi/VirtualBox, VMsy ]. El shell analiza -d /home/mazimi/VirtualBoxy luego no sabe qué hacer VMs. Debería evitar la división de palabras ${dir}para obtener un comando bien formado.

En términos generales, siempre use comillas dobles alrededor de las sustituciones de variables y comandos a menos que sepa que desea dividir y pegar palabras en el resultado. Los principales lugares donde es seguro no usar las comillas dobles son:

  • en una asignación: foo=$bar(pero tenga en cuenta que necesita las comillas dobles en export "foo=$bar"o en asignaciones de matriz como array=("$a" "$b"));
  • en una casedeclaración: case $foo in …;
  • dentro de los paréntesis dobles excepto en el lado derecho del =o de ==operador (a menos que usted desea que la coincidencia de patrones): [[ $x = "$y" ]].

En todo esto, es correcto usar comillas dobles, por lo que es mejor omitir las reglas avanzadas y usar las comillas todo el tiempo.

Gilles
fuente
1
@StephaneChazelas Un error mío. Resulta que de [hecho es del Sistema III en 1981 , pensé que era más viejo.
Gilles
8

¿Cuándo debo asignar comillas dobles alrededor de variables como "${var}"para evitar problemas causados ​​por espacios?

Implícito en esta pregunta es

¿Por qué no es lo suficientemente bueno?${variable_name}

${variable_name} no significa lo que crees que hace ...

... si cree que tiene algo que ver con problemas causados ​​por espacios (en valores variables). es bueno para esto:${variable_name}

$ bar=foo
$ bard=Shakespeare
$ echo $bard
Shakespeare
$ echo ${bar}d
food

¡y nada más! 1  nosirve de nada a menos que lo esté siguiendo inmediatamente con un carácter que podría ser parte de un nombre de variable: una letra (-o-), un guión bajo () o un dígito (-). E incluso entonces, puedes solucionarlo:${variable_name}AZaz_09

$ echo "$bar"d
food

No estoy tratando de desalentar su uso, echo "${bar}d"es probable que sea la mejor solución aquí, sino de disuadir a las personas de depender de llaves en lugar de citas, o aplicar llaves instintivamente y luego preguntar: “Ahora, ¿también necesito citas ? "  Siempre debe usar comillas a menos que tenga una buena razón para no hacerlo, y esté seguro de saber lo que está haciendo.
_________________
1    Excepto, por supuesto, el hecho de que las formas más elegantes de expansión de parámetros , por ejemplo, y se basan en la sintaxis. Además, es necesario utilizar , etc., para hacer referencia a los días 10, 11, etc., parámetros posicionales - cotizaciones no le ayudará con eso.${parameter:-[word]}${parameter%[word]}${parameter}${10}${11}

G-Man
fuente