¿Es preferible el uso de corchetes dobles [[]] sobre corchetes simples [] en Bash?

575

Un compañero de trabajo afirmó recientemente en una revisión de código que la [[ ]]construcción es preferible [ ]a las construcciones como

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

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

Leonard
fuente
19
Sea flexible, algunas veces permítase escuchar un consejo sin requerir una explicación profunda :) En cuanto [[a él, el código es bueno y claro, pero recuerde ese día cuando portará sus scripts en el sistema con un shell predeterminado que no es , basho kshetc. [es más feo, engorroso, pero funciona como AK-47en cualquier situación.
torre
178
@rook Puedes escuchar un consejo sin una explicación profunda, claro. Pero cuando solicita una explicación y no la obtiene, generalmente es una bandera roja. "Confía, pero verifica" y todo eso.
Josip Rodin
66
Ver también: ¿Cuál es la diferencia entre los operadores Bash [[vs [vs (vs ((? En Unix y Linux SE.
codeforester
1
@rook en otras palabras "haz lo que te dicen y no hagas preguntas"
glifo
@rook Eso no tenía sentido.
Rakib

Respuestas:

609

[[tiene menos sorpresas y generalmente es más seguro de usar. Pero no es portátil: POSIX no especifica lo que 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*"(donde lo [[lleva 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 incorporado, pero aún no tiene este manejo especial).

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

Johannes Schaub - litb
fuente
21
Teniendo en cuenta que bash está en todas partes en estos días, tiendo a pensar que es bastante portátil. La única excepción común para mí es en las plataformas busybox, pero de todos modos es probable que desee hacer un esfuerzo especial, dado el hardware en el que se ejecuta.
pistolas
14
@guns: De hecho. Sugeriría que su segunda oración refuta la primera; si considera el desarrollo de software en su conjunto, "bash está en todas partes" y "la excepción son las plataformas busybox" son completamente incompatibles. busybox está muy extendido para el desarrollo integrado.
Carreras de ligereza en órbita
11
@guns: incluso si bash está instalado en mi caja, es posible que no esté ejecutando el script (podría tener / bin / sh vinculado a alguna variante de guión, como es estándar en FreeBSD y Ubuntu). También hay rarezas adicionales con Busybox: con las opciones de compilación estándar hoy en día, analiza [[ ]]pero lo interpreta con el mismo significado que [ ].
dubiousjim
59
@dubiousjim: Si usa construcciones de solo bash (como esta), debería tener #! / bin / bash, no #! / bin / sh.
Nick Matteo
16
Es por eso que uso mis scripts #!/bin/shpero luego los cambio para usarlos #!/bin/bashtan pronto como confío en alguna característica específica de BASH, para denotar que ya no es Bourne shell portable.
Anthony
151

Diferencias de comportamiento

Algunas diferencias en Bash 4.3.11:

  • Extensión POSIX vs Bash:

  • comando regular vs magia

    • [ es solo un comando regular con un nombre extraño.

      ]es solo un argumento [que evita que se utilicen más argumentos.

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

      Nada se altera en la forma en que Bash analiza el comando.

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

    • [[ X ]]es una construcción única que hace que Xse analice 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 incorporado y [[es una palabra clave: /ubuntu/445749/whats-the-difference-between-shell-builtin-and-shell-keyword

  • <

  • && 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 -a b = b ]: equivalente, pero obsoleto por POSIX³
    • [ a = a ] && [ b = b ]: POSIX y equivalente confiable
  • (

    • [[ (a = a || a = b) && a = b ]]: falso
    • [ ( a = a ) ]: error de sintaxis, ()se interpreta como una subshell
    • [ \( a = a -o a = b \) -a a = b ]: equivalente, pero ()es obsoleto por POSIX
    • { [ a = a ] || [ a = b ]; } && [ a = b ] POSIX equivalente⁵
  • división de palabras y generación de nombre de archivo en expansiones (split + glob)

    • x='a b'; [[ $x = 'a b' ]]: cierto, no se necesitan citas
    • 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? ]]: cierto, porque hace coincidir patrones ( * ? [son mágicos). No se expande globalmente a los archivos en el directorio actual.
    • [ ab = a? ]: a?Glob se expande. Por lo tanto, puede ser verdadero o falso dependiendo de los archivos en el directorio actual.
    • [ ab = a\? ]: falso, no expansión glob
    • =y ==son iguales en ambos [y [[, pero ==es una extensión Bash.
    • case ab in (a?) echo match; esac: Equivalente POSIX
    • [[ ab =~ 'ab?' ]]: falso⁴, pierde magia con ''
    • [[ ab? =~ 'ab?' ]]: cierto
  • =~

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

Recomendación : usar siempre [].

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

Si te [[ ]]usas:

  • perder portabilidad
  • obligar al lector a aprender las complejidades de otra extensión bash. [es solo un comando regular con un nombre extraño, no hay semántica especial involucrada.

¹ Inspirado en la [[...]]construcción equivalente en el shell Korn

² pero falla para algunos valores de ao b(like +o index) y hace una comparación numérica si ay se bparecen a enteros decimales. expr "x$a" '<' "x$b"trabaja alrededor de ambos.

³ y también falla para algunos valores de ao blike !or (.

Bas en bash 3.2 y superior y la compatibilidad proporcionada a bash 3.1 no está habilitada (como con BASH_COMPAT=3.1)

⁵ aunque la agrupación (aquí con el {...;}grupo de comandos en lugar del (...)cual se ejecutaría una subshell innecesaria) no es necesaria ya que los operadores de shell ||y &&(en oposición a los operadores ||y && [[...]]o los operadores -o/ -a [) tienen la misma prioridad. Entonces [ a = a ] || [ a = b ] && [ a = b ]sería equivalente.

Ciro Santilli 郝海东 冠状 病 六四 事件 法轮功
fuente
44
Buenas explicaciones Yo uso [por las mismas razones.
Gordon
2
En Bashese :) ha. Buena aclaración de las distinciones y razones para seguir con POSIX.
Jonathan Komar
56

[[ ]]tiene más funciones: le sugiero que eche un vistazo a la Guía avanzada de secuencias de comandos de Bash para obtener más información, específicamente la sección de comando de prueba extendido en el Capítulo 7. Pruebas .

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

Nils von Barth
fuente
55
Esto está lejos de ser un bashismo, se introdujo por primera vez en la concha de Korn.
Henk Langeveld
66
@Thomas, el ABS en realidad se considera una referencia muy pobre en muchos círculos; Si bien tiene una gran cantidad de información precisa, tiende a tener muy poco cuidado para evitar mostrar malas prácticas en sus ejemplos, y pasó gran parte de su vida sin mantenimiento.
Charles Duffy
@CharlesDuffy gracias por tu comentario, ¿puedes nombrar una buena alternativa? No soy un experto en scripting de shell, estoy buscando una guía que pueda consultar para escribir un script una vez cada medio año.
Thomas
99
@Thomas, Wooledge BashFAQ y wiki asociado son lo que uso; son mantenidos activamente por los habitantes del canal Freenode #bash (quienes, aunque a veces espinosos, tienden a preocuparse profundamente por la corrección). BashFAQ # 31, mywiki.wooledge.org/BashFAQ/031 , es la página directamente relevante para esta pregunta.
Charles Duffy
13

¿ Desde qué comparador, prueba, paréntesis o paréntesis doble es el más rápido? ( http://bashcurescancer.com )

El paréntesis doble es un "comando compuesto" donde, como prueba, y el paréntesis único son elementos integrados de shell (y en realidad son el mismo comando). Por lo tanto, el corchete simple y el corchete doble ejecutan un 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 está utilizando una versión remotamente moderna de BASH, se admite el soporte doble.

f3lix
fuente
77
¿Qué pasa con la obsesión de los scripts de shell más rápidos ? Lo quiero más portátil y no podría importarme menos la mejora que [[podría traer. Pero entonces, soy un viejo pedo de la vieja escuela :-)
Jens
1
Creo que muchos shells prueban versiones integradas de, [y testaunque también existen versiones externas.
dubiousjim
44
@Jens en general, estoy de acuerdo: todo el propósito de los scripts es (¿era?) Portabilidad (de lo contrario, codificaríamos y compilaríamos, no script) ... las dos excepciones que puedo pensar son: (1) completar la pestaña (donde los scripts de finalización pueden ser muy largos con mucha lógica condicional); y (2) superpreguntas ( PS1=...crazy stuff...y / o $PROMPT_COMMAND); para estos, no quiero ningún retraso perceptible en la ejecución del script.
michael
1
Parte de la obsesión con el más rápido es simplemente el estilo. En igualdad de condiciones, ¿por qué no incorporar la construcción de código más eficiente en su estilo predeterminado, especialmente si esa construcción también ofrece más legibilidad? En cuanto a la portabilidad, muchas tareas para las que bash es adecuado son inherentemente no portátiles. Por ejemplo, necesito correr apt-get updatesi han pasado más de X horas desde la última vez que se ejecutó. Es un gran alivio cuando uno puede dejar la portabilidad fuera de la lista ya demasiado larga de restricciones para el código.
Ron Burk
5

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

Prueba, [y[[

[[ ... ]]reduce los errores ya que no hay expansión de nombre de ruta o división de palabras entre [[y ]], y [[ ... ]]permite la coincidencia de expresiones regulares donde [ ... ]no es así.

# 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.
# For the gory details, see
# E14 at https://tiswww.case.edu/php/chet/bash/FAQ
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
crizCraig
fuente
1
Google escribió " no hay expansión de nombre de ruta ... tiene lugar " pero [[ -d ~ ]]devuelve verdadero (lo que implica que ~se expandió a /home/user) Creo que Google debería haber sido más preciso en su escritura.
JamesThomasMoon1979
2

Una situación típica en la que no puede usar [[es en un script configure.ac de autotools, allí los corchetes tienen un significado especial y diferente, por lo que tendrá que usar en testlugar de [o [[- Tenga en cuenta que prueba y [son el mismo programa.

Vicente Bolea
fuente
2
Las herramientas automáticas dadas no son un shell POSIX, ¿por qué alguna vez esperarías [ser definido como una función de shell POSIX?
Gordon
Debido a que el script de autoconf se parece a un script de shell, produce un script de shell y la mayoría de los comandos de shell operan dentro de él.
vy32
-1

[[]] los corchetes dobles no son compatibles con ciertas versiones de SunOS y no son totalmente compatibles con las declaraciones de funciones internas de: GNU bash, versión 2.02.0 (1) -release (sparc-sun-solaris2.6)

carroñero
fuente
1
muy cierto, y nada inconsecuente. la portabilidad de bash en versiones anteriores debe considerarse. La gente dice "bash es omnipresente y portátil, excepto tal vez (inserte el sistema operativo esotérico aquí) ", pero en mi experiencia, solaris es una de esas plataformas en las que se debe prestar especial atención a la portabilidad: no solo para considerar las versiones antiguas de bash en un SO más reciente, problemas / errores con matrices, funciones, etc. pero incluso las utilidades (utilizadas en los scripts) como tr, sed, awk, tar tienen rarezas y peculiaridades en solaris con las que tiene que solucionar.
michael
2
tienes razón ... muchas utilidades que no son POSIX en Solaris, solo mira la salida "df" y los argumentos ... Vergüenza en Sun. Esperemos que esté desapareciendo poco a poco (excepto en Canadá).
carroñero
77
Solaris 2.6 en serio? Fue lanzado en 1997 y finalizó el soporte en 2006. ¡Supongo que si todavía lo estás usando, entonces tienes otros problemas! Por cierto, utilizó Bash v2.02, que fue el que introdujo los corchetes dobles, por lo que debería funcionar incluso en algo tan antiguo como eso. Solaris 10 de 2005 usaba Bash 3.2.51 y Solaris 11 de 2011 usa Bash 4.1.11.
Peter
1
Re Script portabilidad. En los sistemas Linux generalmente solo hay una edición de cada herramienta y esa es la edición GNU. En Solaris, normalmente puede elegir entre una edición nativa de Solaris o la edición GNU (por ejemplo, tar de Solaris vs tar de GNU). Si depende de extensiones específicas de GNU, debe indicarlo en su script para que sea portátil. En Solaris, puede hacerlo con el prefijo "g", por ejemplo, `ggrep` si desea GNU grep.
Peter
-2

En pocas palabras, [[es mejor porque no bifurca otro proceso. Ningún paréntesis o un solo paréntesis es más lento que uno doble porque bifurca otro proceso.

unix4linux
fuente
8
Prueba y [son nombres para el mismo comando incorporado en bash. Intenta usar type [para ver esto.
AB
1
@alberge, eso es cierto, pero [[, a diferencia de [, es la sintaxis interpretada por el intérprete de línea de comandos bash. En bash, intente escribir type [[. unix4linux es correcto que, aunque las [pruebas de Bourne-shell clásicas completan un nuevo proceso para determinar el valor de verdad, la [[sintaxis (tomada de ksh por bash, zsh, etc.) no lo hace.
Tim Gilbert
2
@Tim, no estoy seguro de qué shell Bourne estás hablando, pero [está integrado tanto en Bash como en Dash ( /bin/shen todas las distribuciones de Linux derivadas de Debian).
AB
1
Oh, ya veo lo que quieres decir, es verdad. Estaba pensando en algo como, por ejemplo, / bin / sh en sistemas Solaris o HP / UX más antiguos, pero, por supuesto, si necesitaras ser compatible con los que no estarías usando [[tampoco.
Tim Gilbert
1
@alberge Bourne shell no es Bash (también conocido como Bourne Again SHell ).
kiamlaluno