¿Cuál es la diferencia entre los operadores Bash [[vs [vs (vs ((?

247

Estoy un poco confundido sobre qué hacen estos operadores de manera diferente cuando se usan en bash (corchetes, corchetes dobles, paréntesis y paréntesis dobles).

[[ , [ , ( , ((

He visto a personas usarlas en declaraciones if como esta:

if [[condition]]

if [condition]

if ((condition))

if (condition)
RetroCode
fuente
44
Es posible que desee ver en unix.stackexchange.com/questions/tagged/test primera
cuonglm
3
@cuonglm Ironic porque ese enlace produce esta pregunta como primer resultado. ¡Paradoja!
Loco
55
¿Supongo que leer la documentación no es una opción?
Carreras de ligereza en órbita
34
Los paréntesis y los corchetes no son tan fáciles de buscar en la documentación, y eso es todo lo que tiene si no conoce los nombres de esas características.
ilkkachu

Respuestas:

259

Una ifdeclaración típicamente se parece a

if commands1
then
   commands2
else
   commands3
fi

La thencláusula se ejecuta si el código de salida de commands1es cero. Si el código de salida no es cero, elsese ejecuta la cláusula. commands1puede ser simple o complejo Se puede, por ejemplo, ser una secuencia de uno o más tuberías separadas por uno de los operadores ;, &, &&, o ||. Las ifcondiciones que se muestran a continuación son solo casos especiales de commands1:

  1. if [ condition ]

    Este es el testcomando de shell tradicional . Está disponible en todos los shells POSIX. El comando de prueba establece un código de salida y la ifinstrucción actúa en consecuencia. Las pruebas típicas son si existe un archivo o si un número es igual a otro.

  2. if [[ condition ]]

    Esta es una nueva variación mejorada testde ksh que bash y zsh también admiten. Este testcomando también establece un código de salida y la ifinstrucción actúa en consecuencia. Entre sus características extendidas, puede probar si una cadena coincide con una expresión regular.

  3. if ((condition))

    Otra extensión de ksh que también admiten bash y zsh . Esto realiza aritmética. Como resultado de la aritmética, se establece un código de salida y la ifinstrucción actúa en consecuencia. Devuelve un código de salida de cero (verdadero) si el resultado del cálculo aritmético es distinto de cero. Al igual [[...]], este formulario no es POSIX y, por lo tanto, no es portátil.

  4. if (command)

    Esto ejecuta el comando en una subshell. Cuando se completa el comando, establece un código de salida y la ifinstrucción actúa en consecuencia.

    Una razón típica para el uso de un subnivel de este tipo es para limitar los efectos secundarios de commandsi commandse requiere asignaciones de variables u otros cambios en el entorno de la cáscara. Tales cambios no permanecen después de que se completa la subshell.

  5. if command

    El comando se ejecuta y la ifinstrucción actúa de acuerdo con su código de salida.

John1024
fuente
24
Gracias por incluir la quinta opción. Esa es la clave para entender cómo funciona esto y está sorprendentemente subutilizado.
pollitos
44
Tenga en cuenta que en [realidad es un binario, no un comando o símbolo interno. Generalmente vive en /bin.
Julien R.
8
@JulienR. en realidad [está integrado, tal como está test. Hay versiones binarias disponibles por razones de compatibilidad. Echa un vistazo help [y help test.
OldTimer
44
Vale la pena señalar que while ((no es POSIX, $((es decir , la expansión aritmética es y es fácil confundirlos. A menudo, una solución alternativa es usar algo como [ $((2+2)) -eq 4 ]hacer uso de la aritmética en declaraciones condicionales
Sergiy Kolodyazhnyy
1
Desearía poder votar esta respuesta más de una vez. Explicación perfecta
Anthony Gatlin hace
77
  • (…)los paréntesis indican una subshell . Lo que hay dentro de ellos no es una expresión como en muchos otros idiomas. Es una lista de comandos (al igual que los paréntesis externos). Estos comandos se ejecutan en un subproceso separado, por lo que cualquier redirección, asignación, etc. realizada dentro de los paréntesis no tiene efecto fuera de los paréntesis.
    • Con un signo de dólar inicial, $(…)es una sustitución de comando : hay un comando dentro de los paréntesis, y la salida del comando se usa como parte de la línea de comando (después de expansiones adicionales a menos que la sustitución sea entre comillas dobles, pero esa es otra historia ) .
  • { … }Las llaves son como paréntesis en el sentido de que agrupan comandos, pero solo influyen en el análisis, no en la agrupación. El programa x=2; { x=4; }; echo $ximprime 4, mientras que x=2; (x=4); echo $ximprime 2. (También los corchetes que son palabras clave deben delimitarse y encontrarse en la posición del comando (de ahí el espacio después {y ;antes }), mientras que los paréntesis no. Eso es solo una peculiaridad de sintaxis).
    • Con un signo de dólar principal, ${VAR}es una expansión de parámetros , que se expande al valor de una variable, con posibles transformaciones adicionales. El ksh93shell también admite una ${ cmd;}forma de sustitución de comandos que no genera un subshell.
  • ((…))los paréntesis dobles rodean una instrucción aritmética , es decir, un cálculo en enteros, con una sintaxis similar a otros lenguajes de programación. Esta sintaxis se usa principalmente para asignaciones y en condicionales. Esto solo existe en ksh / bash / zsh, no en sh simple.
    • La misma sintaxis se usa en expresiones aritméticas $((…)), que se expanden al valor entero de la expresión.
  • [ … ]Los corchetes simples rodean las expresiones condicionales . Las expresiones condicionales se basan principalmente en operadores como -n "$variable"para probar si una variable está vacía y -e "$file"para probar si existe un archivo. Tenga en cuenta que necesita un espacio alrededor de cada operador (por ejemplo [ "$x" = "$y" ], no [ "$x"="$y" ]), y un espacio o un carácter como ;dentro y fuera de los corchetes (por ejemplo [ -n "$foo" ], no [-n "$foo"]).
  • [[ … ]]los corchetes dobles son una forma alternativa de expresiones condicionales en ksh / bash / zsh con algunas características adicionales, por ejemplo, puede escribir [[ -L $file && -f $file ]]para probar si un archivo es un enlace simbólico a un archivo normal, mientras que los corchetes simples requieren [ -L "$file" ] && [ -f "$file" ]. Consulte ¿Por qué la expansión de parámetros con espacios sin comillas funciona entre corchetes dobles [[pero no entre corchetes [? para más sobre este tema

En el shell, cada comando es un comando condicional: cada comando tiene un estado de retorno que es 0 que indica éxito o un número entero entre 1 y 255 (y potencialmente más en algunos shells) que indica falla. El [ … ]comando (o [[ … ]]forma de sintaxis) es un comando particular que también se puede deletrear test …y tiene éxito cuando existe un archivo, o cuando una cadena no está vacía, o cuando un número es más pequeño que otro, etc. La ((…))forma de sintaxis tiene éxito cuando un número es distinto de cero Aquí hay algunos ejemplos de condicionales en un script de shell:

  • Prueba si myfilecontiene la cadena hello:

    if grep -q hello myfile; then 
  • Si mydires un directorio, cámbielo y haga cosas:

    if cd mydir; then
      echo "Creating mydir/myfile"
      echo 'some content' >myfile
    else
      echo >&2 "Fatal error. This script requires mydir to exist."
    fi
    
  • Pruebe si hay un archivo llamado myfileen el directorio actual:

    if [ -e myfile ]; then 
  • Lo mismo, pero también incluye enlaces simbólicos colgantes:

    if [ -e myfile ] || [ -L myfile ]; then 
  • Pruebe si el valor de x(que se supone que es numérico) es al menos 2, de forma portátil:

    if [ "$x" -ge 2 ]; then 
  • Pruebe si el valor de x(que se supone que es numérico) es al menos 2, en bash / ksh / zsh:

    if ((x >= 2)); then 
Gilles
fuente
Tenga en cuenta que el soporte único admite el en -alugar de &&, por lo que se puede escribir:, [ -L $file -a -f $file ]que es el mismo número de caracteres dentro de los corchetes sin el extra [y ]...
Alexis Wilke
66
@AlexisWilke Los operadores -ay -oson problemáticos porque pueden conducir a análisis incorrectos si algunos de los operandos involucrados parecen operadores. Por eso no los menciono: tienen cero ventajas y no siempre funcionan. Y nunca escriba expansiones de variables sin comillas sin una buena razón: [[ -L $file -a -f $file ]]está bien, pero con paréntesis individuales necesita [ -L "$file" -a -f "$file" ](lo cual está bien, por ejemplo, si $filesiempre comienza con /o ./).
Gilles
Tenga en cuenta que es [[ -L $file && -f $file ]](no -acon la [[...]]variante).
Stéphane Chazelas
18

De la documentación de bash :

(list)La lista se ejecuta en un entorno de subshell (ver ENTORNO DE EJECUCIÓN DE MANDOS a continuación). Las asignaciones variables y los comandos incorporados que afectan el entorno del shell no permanecen vigentes una vez que se completa el comando. El estado de retorno es el estado de salida de la lista.

En otras palabras, se asegura de que lo que suceda en 'lista' (como a cd) no tenga ningún efecto fuera de (y ). La única cosa que se escape es el código de salida del último comando o con set -eel primer comando que genera un error (aparte de unos pocos, como if, while, etc.)

((expression))La expresión se evalúa de acuerdo con las reglas que se describen a continuación en EVALUACIÓN ARITMÉTICA. Si el valor de la expresión no es cero, el estado de retorno es 0; de lo contrario, el estado de retorno es 1. Esto es exactamente equivalente a dejar "expresión".

Esta es una extensión bash que te permite hacer matemáticas. Esto es algo similar a usar exprsin todas las limitaciones de expr(como tener espacios en todas partes, escapar *, etc.)

[[ expression ]]Devuelve un estado de 0 o 1 dependiendo de la evaluación de la expresión de expresión condicional. Las expresiones se componen de las primarias que se describen a continuación en EXPRESIONES CONDICIONALES. La división de palabras y la expansión del nombre de ruta no se realizan en las palabras entre [[y]]; Se realiza la expansión tilde, la expansión de parámetros y variables, la expansión aritmética, la sustitución de comandos, la sustitución de procesos y la eliminación de comillas. Los operadores condicionales como -f deben estar sin comillas para ser reconocidos como primarios.

Cuando se usa con [[, los operadores <y> se ordenan lexicográficamente usando la localización actual.

Esto ofrece una prueba avanzada para comparar cadenas, números y archivos un poco como las testofertas, pero más potente.

[ expr ]Devuelve un estado de 0 (verdadero) o 1 (falso) dependiendo de la evaluación de la expresión condicional expr. Cada operador y operación y debe ser un argumento separado. Las expresiones se componen de las primarias descritas anteriormente en EXPRESIONES CONDICIONALES. test no acepta ninguna opción, ni acepta e ignora un argumento de - como el final de las opciones.

[...]

Este llama test. En realidad, en los viejos tiempos, [era un enlace simbólico a test. Funciona de la misma manera y tienes las mismas limitaciones. Dado que un binario conoce el nombre con el que se inició, el programa de prueba puede analizar los parámetros hasta que encuentre un parámetro ]. Divertidos trucos de Unix.

Tenga en cuenta que, en el caso de bash, [y testson funciones integradas (como se menciona en un comentario), se aplican prácticamente las mismas limitaciones.

Alexis Wilke
fuente
1
Aunque testy [, por supuesto, son comandos incorporados en Bash, pero es probable que también exista un binario externo.
ilkkachu
1
El binario externo para [no es un enlace simbólico testen la mayoría de los sistemas modernos.
Random832
1
De alguna manera, me parece divertido que se molesten en crear dos binarios separados, que tienen exactamente lo que necesitan, en lugar de combinarlos y agregar un par de condicionales. Aunque en realidad strings /usr/bin/testmuestra que también tiene el texto de ayuda, así que no sé qué decir.
ilkkachu
2
@ Random832 Entiendo su punto de vista sobre la lógica de GNU para evitar un comportamiento arg0 inesperado, pero sobre los requisitos POSIX, no sería tan afirmativo. Si bien el testestándar obviamente requiere que exista como un comando independiente basado en un archivo según el estándar, nada en él indica que su [variante deba implementarse de esa manera también. Por ejemplo, Solaris 11 no proporciona ningún [ejecutable pero, sin embargo, es totalmente compatible con los estándares POSIX
jlliagre,
2
(salida 1) tiene un efecto fuera de los paréntesis.
Alexander
14

[ vs [[

Esta respuesta cubrirá el subconjunto [vs [[de la pregunta.

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 para él /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: https://askubuntu.com/questions/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 5
  • 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? ]: el a?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 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 4 , 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 (.

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

5 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
@ StéphaneChazelas gracias por la información! He agregado expra la respuesta. El término "extensión de Bash" no implica que Bash fue el primer intérprete que agregó algo de sintaxis, aprender POSIX sh vs Bash ya es suficiente para volverme loco.
Ciro Santilli 新疆 改造 中心 法轮功 六四 事件
Mira man testsi lo intentaste man [y te perdiste. Eso explicará la variante POSIX.
Jonathan Komar
13

Algunos ejemplos:

Prueba tradicional:

foo="some thing"
# check if value of foo is not empty
if [ -n "$foo" ] ; then... 
if test -n "$foo" ; then... 

testy [son comandos como cualquier otro, por lo que la variable se divide en palabras a menos que esté entre comillas.

Prueba de nuevo estilo

[[ ... ]] es una construcción de shell especial (más reciente), que funciona de manera un poco diferente, lo más obvio es que no tiene variables de división de palabras:

if [[ -n $foo ]] ; then... 

Alguna documentación sobre [y [[aquí .

Prueba aritmética:

foo=12 bar=3
if (( $foo + $bar == 15 )) ; then ...  

Comandos "normales":

Todo lo anterior actúa como comandos normales y ifpuede tomar cualquier comando:

# grep returns true if it finds something
if grep pattern file ; then ...

Múltiples comandos:

O podemos usar múltiples comandos. Al envolver un conjunto de comandos, los ( ... )ejecuta en subshell, creando una copia temporal del estado del shell (directorio de trabajo, variables). Si necesitamos ejecutar algún programa temporalmente en otro directorio:

# this will move to $somedir only for the duration of the subshell 
if ( cd $somedir ; some_test ) ; then ...

# while here, the rest of the script will see the new working
# directory, even after the test
if cd $somedir ; some_test ; then ...
ilkkachu
fuente
1

Comandos de agrupamiento

Bash proporciona dos formas de agrupar una lista de comandos para ejecutarlos como una unidad.

( list )Al colocar una lista de comandos entre paréntesis, se crea un entorno de subshell y cada uno de los comandos de la lista se ejecuta en esa subshell. Dado que la lista se ejecuta en una subshell, las asignaciones de variables no permanecen vigentes una vez que se completa la subshell.

$ a=1; (a=2; echo "inside: a=$a"); echo "outside: a=$a"
inside: a=2
outside: a=1

{ list; }Colocar una lista de comandos entre llaves hace que la lista se ejecute en el contexto actual del shell . No se crea ninguna subshell. Se requiere la siguiente lista de punto y coma (o nueva línea). Fuente

${} Parameter expansion Ex:  ANIMAL=duck; echo One $ANIMAL, two ${ANIMAL}s
$() Command substitution Ex: result=$(COMMAND) 
$(()) Arithmetic expansion Ex: var=$(( 20 + 5 )) 

Construcciones condicionales

Soporte simple, es decir, []
para comparación ==, !=, <,y >debe usarse y para comparación numérica eq, ne,lty gtdebe usarse.

Brackets mejorados, es decir[[]]

En todos los ejemplos anteriores, utilizamos solo corchetes para encerrar la expresión condicional, pero bash permite corchetes dobles que sirve como una versión mejorada de la sintaxis de un solo corchete.

A modo de comparación ==, !=, <,y >puede usar literalmente.

  • [es sinónimo de comando de prueba. Incluso si está integrado en el shell, crea un nuevo proceso.
  • [[ es una nueva versión mejorada de la misma, que es una palabra clave, no un programa.
  • [[se entiende por Korny Bash.

Fuente

Premraj
fuente