¿Son preferibles los corchetes dobles [[ ]] a los corchetes simples [ ] en Bash?

Resuelto Leonard asked hace 15 años • 11 respuestas

Un compañero de trabajo afirmó recientemente en una revisión de código que [[ ]]se debe preferir la construcción [ ]a construcciones como

if [ "`id -nu`" = "$someuser" ] ; then
     echo "I love you madly, $someuser"
fi

No pudo dar una justificación. ¿Hay uno?

Leonard avatar Mar 21 '09 22:03 Leonard
Aceptado

[[tiene menos sorpresas y generalmente es más seguro de usar. Pero no es portátil: POSIX no especifica qué hace y solo algunos shells lo admiten (además de bash, escuché que ksh también lo admite). Por ejemplo, puedes hacer

[[ -e $b ]]

para probar si existe un archivo. Pero con [, tienes que citar $b, porque divide el argumento y expande cosas como "a*"(dónde [[lo toma literalmente). Eso también tiene que ver con cómo [puede ser un programa externo y recibe su argumento normalmente como cualquier otro programa (aunque también puede ser un programa integrado, pero todavía no tiene este manejo especial).

[[también tiene otras características interesantes, como la coincidencia de expresiones regulares junto =~con operadores como se conocen en lenguajes tipo C. Aquí hay una buena página al respecto: ¿ Cuál es la diferencia entre prueba [y [[? y pruebas de bash

Johannes Schaub - litb avatar Mar 21 '2009 15:03 Johannes Schaub - litb

Diferencias de comportamiento

Algunas diferencias en Bash 4.3.11:

  • Extensión POSIX frente a Bash:

    • [ es POSIX
    • [[ es una extensión de Bash inspirada en KornShell
  • comando regular vs magia

    • [es sólo un comando normal con un nombre extraño.

      ]es solo el último argumento de [.

      Ubuntu 16.04 en realidad tiene un ejecutable proporcionado /usr/bin/[por coreutils , pero la versión integrada de Bash tiene prioridad.

      No se modifica nada en la forma en que Bash analiza el comando.

      En particular, <es la redirección &&y ||la concatenación de múltiples comandos, ( )genera subcapas a menos que se escape mediante \, y la expansión de palabras ocurre como de costumbre.

    • [[ X ]]Es una construcción única que se puede Xanalizar mágicamente. <, &&y se tratan de forma especial y las reglas de división de palabras son diferentes ||.()

      También hay otras diferencias como =y =~.

    En Bashese: [es un comando integrado y [[es una palabra clave: ¿Cuál es la diferencia entre el shell incorporado y la palabra clave del shell?

  • <

    • [[ a < b ]]: comparación lexicográfica
    • [ a \< b ]: Lo mismo que arriba. \requerido o de lo contrario realiza la redirección como para cualquier otro comando. Extensión de bash.
    • expr x"$x" \< x"$y" > /dev/nullo [ "$(expr x"$x" \< x"$y")" = 1 ]: equivalentes POSIX, consulte: ¿ Cómo probar cadenas lexicográficas menores o iguales en Bash?
  • &&y||

    • [[ a = a && b = b ]]: verdadero, lógico y
    • [ a = a && b = b ]: error de sintaxis, &&analizado como un separador de comando ANDcmd1 && cmd2
    • [ a = a ] && [ b = b ]: equivalente confiable de POSIX
    • [ a = a -a b = b ]: casi equivalente, pero POSIX lo desaprueba porque es una locura y falla para algunos valores de ao blike !o (que se interpretarían como operaciones lógicas
  • (

    • [[ (a = a || a = b) && a = b ]]: FALSO. Sin ( )ello sería verdad, porque [[ && ]]tiene mayor precedencia que[[ || ]]
    • [ ( a = a ) ]: error de sintaxis, ()se interpreta como una subcapa
    • [ \( a = a -o a = b \) -a a = b ]: equivalente, pero (), -ay -oestán en desuso por POSIX. Sin \( \)ello sería verdad, porque -atiene mayor precedencia que-o
    • { [ a = a ] || [ a = b ]; } && [ a = b ]Equivalente POSIX no obsoleto. Sin embargo, en este caso particular, podríamos haber escrito simplemente: [ a = a ] || [ a = b ] && [ a = b ], porque los operadores de shell ||y &&tienen la misma precedencia, a diferencia de [[ || ]]y [[ && ]]y -o, -ay[
  • división de palabras y generación de nombres de archivos tras expansiones (split+glob)

    • x='a b'; [[ $x = 'a b' ]]: verdadero. No se necesitan cotizaciones
    • x='a b'; [ $x = 'a b' ]: error de sintaxis. Se expande a[ a b = 'a b' ]
    • x='*'; [ $x = 'a b' ]: error de sintaxis si hay más de un archivo en el directorio actual.
    • x='a b'; [ "$x" = 'a b' ]: equivalente POSIX
  • =

    • [[ ab = a? ]]: verdadero, porque coincide con patrones ( * ? [son mágicos). No se expande globalmente a los archivos del directorio actual.
    • [ ab = a? ]: a?el globo se expande. Por lo tanto, puede ser verdadero o falso dependiendo de los archivos en el directorio actual.
    • [ ab = a\? ]: falso, no expansión global
    • =y ==son iguales en ambos [y [[, pero ==es una extensión de Bash.
    • case ab in (a?) echo match; esac: equivalente POSIX
    • [[ ab =~ 'ab?' ]]: falso, pierde magia ''en Bash 3.2 y superior y siempre que la compatibilidad con Bash 3.1 no esté habilitada (como con BASH_COMPAT=3.1)
    • [[ ab? =~ 'ab?' ]]: verdadero
  • =~

    • [[ ab =~ ab? ]]: verdadero. La expresión regular extendida POSIX coincide y ?no se expande globalmente
    • [ a =~ a ]: error de sintaxis. No hay equivalente de Bash.
    • printf 'ab\n' | grep -Eq 'ab?': Equivalente POSIX (solo datos de una sola línea)
    • awk 'BEGIN{exit !(ARGV[1] ~ ARGV[2])}' ab 'ab?': Equivalente POSIX.

Recomendación: utilizar siempre[]

Hay equivalentes POSIX para cada [[ ]]construcción que he visto.

Si lo usas [[ ]]:

  • perder portabilidad
  • Obligar al lector a aprender las complejidades de otra extensión de Bash. [Es solo un comando normal con un nombre extraño y no implica ninguna semántica especial.

Gracias a Stéphane Chazelas por importantes correcciones y adiciones.

[[ ]]tiene más funciones. Le sugiero que consulte la Guía avanzada de secuencias de comandos Bash para obtener más información, específicamente la sección de comandos de prueba extendidos en el Capítulo 7. Pruebas .

Por cierto, como señala la guía, [[ ]]se introdujo en ksh88 (la versión de 1988 de KornShell ).

 avatar Mar 21 '2009 15:03

Si te gusta seguir la guía de estilo de Google :

Prueba, [ … ]y[[ … ]]

[[ … ]]se prefiere sobre [ … ], testy /usr/bin/[.

[[ … ]]reduce los errores ya que no se produce ninguna expansión del nombre de ruta ni división de palabras entre [[y ]]. Además, [[ … ]]permite la coincidencia de expresiones regulares, mientras que [ … ]no lo hace.

# This ensures the string on the left is made up of characters in
# the alnum character class followed by the string name.
# Note that the RHS should not be quoted here.
if [[ "filename" =~ ^[[:alnum:]]+name ]]; then
  echo "Match"
fi

# This matches the exact pattern "f*" (Does not match in this case)
if [[ "filename" == "f*" ]]; then
  echo "Match"
fi
# This gives a "too many arguments" error as f* is expanded to the
# contents of the current directory
if [ "filename" == f* ]; then
  echo "Match"
fi

Para conocer los detalles sangrientos, consulte E14 en http://tiswww.case.edu/php/chet/bash/FAQ

crizCraig avatar Jun 25 '2019 21:06 crizCraig

¿ De qué comparador, prueba, bracket o doble bracket es más rápido? :

El corchete doble es un “comando compuesto”, mientras que test y el corchete simple son elementos integrados del shell (y en realidad son el mismo comando). Por lo tanto, el corchete simple y el corchete doble ejecutan código diferente.

La prueba y el soporte único son los más portátiles ya que existen como comandos separados y externos. Sin embargo, si utiliza alguna versión remotamente moderna de BASH, se admite el soporte doble.

f3lix avatar Mar 21 '2009 15:03 f3lix

En una pregunta etiquetada como 'bash' que tiene explícitamente "en Bash" en el título, estoy un poco sorprendido por todas las respuestas que dicen que debes evitarlo [[... ]]¡porque solo funciona en bash!

Es cierto que la portabilidad es la principal objeción: si desea escribir un script de shell que funcione en shells compatibles con Bourne incluso si no son bash (o ksh o zsh), debe evitar [[... ]]Si se encuentra en esa situación y desea probar sus scripts de shell en un shell POSIX más estricto, le recomiendo dash; aunque no es estrictamente POSIX (carece del soporte de internacionalización requerido por el estándar y admite algunas cosas que no son POSIX, como variables locales), está mucho más cerca que cualquiera de la trinidad bash/ksh/zsh.

La otra objeción que veo es al menos aplicable dentro del supuesto de bash: que [[... ]]tiene sus propias reglas especiales que debes aprender, mientras que [... ]actúa como un comando más. Esto también es cierto (y el Sr. Santilli trajo los recibos que muestran todas las diferencias), pero es bastante subjetivo si las diferencias son buenas o malas. Personalmente, me resulta liberador que la construcción de doble corchete me permita usar (... )para agrupar, &&para ||lógica booleana, <para >comparación y para expansiones de parámetros sin comillas. Es como su propio pequeño mundo cerrado donde las expresiones funcionan más como lo hacen en los lenguajes de programación tradicionales sin shell de comandos.

Un punto que no he visto planteado es que este comportamiento de [[... ]]es totalmente consistente con el de la construcción de expansión aritmética $((... )), que está especificada por POSIX, y también permite paréntesis sin comillas y operadores booleanos y de desigualdad (que aquí realizan comparaciones numéricas en lugar de léxicas). Básicamente, cada vez que ve los caracteres entre corchetes duplicados, obtiene el mismo efecto de protección de comillas.

(Bash y sus parientes modernos también usan ((... ))– sin el interlineado $– como forencabezado de bucle estilo C o como entorno para realizar operaciones aritméticas; ninguna sintaxis es parte de POSIX).

Así que hay algunas buenas razones para preferir [[... ]]; también existen razones para evitarlo, que pueden ser aplicables o no en su entorno. En cuanto a su compañero de trabajo, "nuestra guía de estilo lo dice" es una justificación válida, hasta donde llega, pero también buscaría una historia de fondo de alguien que entienda por qué la guía de estilo recomienda lo que hace.

Mark Reed avatar Jul 14 '2020 13:07 Mark Reed