Comparación de Condicionales en Bash: Cuándo Usar test, [ y [[

Compara test, corchetes simples y corchetes dobles para que tus condicionales en Bash sean portátiles, seguros y legibles.

Comparación de Condicionales en Bash: Cuándo Usar test, [ y [[

Cuando un condicional en Bash se comporta de manera extraña, el problema suele ser la construcción que elegiste. test, [ ] y [[ ]] se ven similares, pero manejan las comillas, los patrones, las expresiones regulares y la portabilidad de manera diferente.

Esta guía compara las tres formas para que puedas escribir condicionales que sean seguros, legibles y apropiados para el shell en el que realmente se ejecuta tu script.

El Comando test: La Base

El comando test es una de las formas más antiguas y fundamentales de evaluar condiciones en scripts de shell. Es un comando incorporado en la mayoría de los shells modernos y forma parte del estándar POSIX, lo que lo hace altamente portátil. test evalúa una expresión y devuelve un estado de salida de 0 (verdadero) o 1 (falso).

Uso Básico

El comando test toma uno o más argumentos, que forman la expresión a evaluar. Verifica atributos de archivos, comparaciones de cadenas y comparaciones de enteros.

# Verificar si un archivo existe
if test -f "myfile.txt"; then
    echo "myfile.txt existe y es un archivo regular."
fi

# Verificar si dos cadenas son iguales
NOMBRE="Alice"
if test "$NOMBRE" = "Alice"; then
    echo "El nombre es Alice."
fi

# Verificar si un número es mayor que otro
CONTEO=10
if test "$CONTEO" -gt 5; then
    echo "El conteo es mayor que 5."
fi

Operadores Comunes de test

  • Operadores de Archivo: -f (archivo regular), -d (directorio), -e (existe), -s (no vacío), -r (legible), -w (escribible), -x (ejecutable).
  • Operadores de Cadena: = (igual), != (diferente), -z (cadena vacía), -n (cadena no vacía).
  • Operadores de Entero: -eq (igual), -ne (diferente), -gt (mayor que), -ge (mayor o igual que), -lt (menor que), -le (menor o igual que).

Consejo: Siempre pon entre comillas las variables usadas con test (por ejemplo, "$NOMBRE") para evitar problemas con la división de palabras y la expansión de nombres de ruta si el valor de la variable contiene espacios o caracteres glob.

Corchetes Simples [ ]: La Forma de test

La construcción de corchetes simples [ ] es una sintaxis alternativa para el comando test. En muchos shells, [ es un comando incorporado del shell, y los sistemas a menudo también proporcionan un /usr/bin/[ externo. La diferencia clave es que [ requiere un ] de cierre como su último argumento. Al igual que test, cumple con POSIX.

Sintaxis y Semántica

# Equivalente a test -f "myfile.txt"
if [ -f "myfile.txt" ]; then
    echo "myfile.txt existe y es un archivo regular usando [ ]."
fi

# Equivalente a test "$NOMBRE" = "Alice"
NOMBRE="Bob"
if [ "$NOMBRE" != "Alice" ]; then
    echo "El nombre no es Alice."
fi

Observa el espacio obligatorio después de [ y antes de ]. Estos se tratan como argumentos separados para el comando [.

Poner Variables entre Comillas: Un Detalle Crítico

Debido a que [ ] es fundamentalmente el comando test, hereda los mismos comportamientos con respecto a la división de palabras y la expansión de nombres de ruta. Esto significa que las variables sin comillas pueden provocar un comportamiento inesperado o vulnerabilidades de seguridad.

Considera este ejemplo:

#!/bin/bash

ENTRADA="archivo con espacios.txt"

# PELIGROSO: La variable sin comillas causará problemas si ENTRADA contiene espacios
# El shell realizará la división de palabras, tratando "archivo" y "con espacios.txt" como argumentos separados
# lo que lleva a un error de sintaxis o una evaluación incorrecta.
# if [ -f $ENTRADA ]; then echo "Encontrado"; else echo "No encontrado"; fi 

# CORRECTO: Pon la variable entre comillas para tratarla como un solo argumento
if [ -f "$ENTRADA" ]; then
    echo "'archivo con espacios.txt' existe."
else
    echo "'archivo con espacios.txt' no existe o no es un archivo regular."
fi

Sin comillas, $ENTRADA se expandiría a archivo con espacios.txt, y [ -f archivo con espacios.txt ] sería interpretado como un error de sintaxis por el comando [ porque -f espera solo un operando. Poner entre comillas asegura que $ENTRADA se pase como un solo argumento, "archivo con espacios.txt".

Peligros de la División de Palabras y la Expansión de Nombres de Ruta

Tanto test como [ están sujetos a los comportamientos predeterminados del shell de división de palabras y expansión de nombres de ruta (globbing). Si una variable contiene espacios o caracteres glob (*, ?, [ ]) y no está entre comillas, el shell la expandirá antes de que test o [ vean los argumentos. Esto puede provocar errores de sintaxis o comparaciones incorrectas cuando los caracteres glob coinciden con archivos existentes.

Corchetes Dobles [[ ]]: La Palabra Clave Moderna de Bash

La construcción de corchetes dobles [[ ]] es una palabra clave de Bash (también compatible con Ksh y Zsh), no un comando externo ni un alias. Esta distinción es crucial, ya que permite que [[ ]] se comporte de manera diferente y ofrezca funcionalidad mejorada y mayor seguridad en comparación con test o [ ].

Funcionalidad Mejorada

[[ ]] introduce varias características potentes que no están disponibles con test o [:

  1. Sin División de Palabras ni Expansión de Nombres de Ruta: Las variables dentro de [[ ]] generalmente no necesitan estar entre comillas (aunque a menudo es una buena práctica hacerlo para mayor claridad). El shell maneja el contenido de [[ ]] como una sola unidad, evitando la división de palabras y la expansión de nombres de ruta. Esto reduce significativamente los errores comunes de scripting y los riesgos de seguridad.

    # No es necesario poner variables entre comillas (aunque sigue siendo seguro hacerlo)
    ENTRADA="archivo con espacios.txt"
    if [[ -f $ENTRADA ]]; then # $ENTRADA se trata como una sola cadena aquí
        echo "'$ENTRADA' existe."
    fi
    
  2. Globbing para Comparación de Cadenas: Los operadores == y != realizan coincidencia de patrones (globbing) en lugar de igualdad estricta de cadenas cuando se usan dentro de [[ ]]. Esto significa que puedes usar *, ? y [] como comodines.

    NOMBRE_ARCHIVO="mi_documento.txt"
    if [[ "$NOMBRE_ARCHIVO" == *".txt" ]]; then # Verifica si NOMBRE_ARCHIVO termina en .txt
        echo "¡Es un archivo de texto!"
    fi
    
    # Nota: Para igualdad estricta de cadenas sin globbing, usa `test` o `[ ]` con `=`
    # o asegúrate de que no haya caracteres glob presentes en el lado derecho de `==` en [[ ]] 
    # (o pon entre comillas el lado derecho si contiene caracteres glob literales que deseas coincidir literalmente).
    
  3. Coincidencia de Expresiones Regulares: El operador =~ te permite realizar coincidencias de expresiones regulares.

    DIRECCION_IP="192.168.1.100"
    if [[ "$DIRECCION_IP" =~ ^[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}$ ]]; then
        echo "Formato de IP válido."
    fi

    # Importante: El patrón de expresión regular en el lado derecho de =~ generalmente NO debe estar entre comillas
    # si contiene caracteres que de otro modo se tratarían como patrones glob.
    # Si la expresión regular está en una variable, también debe estar sin comillas.
    # Ejemplo de patrón: ^[A-Za-z]+$
    ```

4.  **Operadores Lógicos `&&` y `||`**: `[[ ]]` admite los operadores lógicos de estilo C `&&` (Y) y `||` (O) más intuitivos para combinar múltiples condiciones, junto con `!` para la negación. Estos operadores tienen una evaluación de cortocircuito y precedencia adecuadas, a diferencia de `-a` y `-o` de `test`.

    ```bash
    EDAD=25
    if [[ "$NOMBRE" == "Alice" && "$EDAD" -ge 18 ]]; then
        echo "Alice es adulta."
    fi

    if [[ "$USUARIO" == "root" || -w /etc/fstab ]]; then
        echo "O es root o puede escribir en fstab."
    fi
    ```

### Naturaleza Específica de Bash

Si bien `[[ ]]` ofrece ventajas significativas, su principal inconveniente es que es una extensión de Bash/Ksh/Zsh y no forma parte del estándar POSIX. Esto significa que los scripts que dependen de `[[ ]]` pueden no ser portátiles a `sh`, `dash` o sistemas Unix-like antiguos/mínimos.

## Comparación Lado a Lado: `test` vs. `[` vs. `[[`

Aquí hay una tabla que resume las diferencias clave:

| Característica               | `test`                           | `[ ]`                               | `[[ ]]`                                   |
| :------------------------- | :------------------------------- | :---------------------------------- | :---------------------------------------- |
| **Tipo**                   | Comando incorporado (o externo)   | Comando incorporado (alias de `test`)  | Palabra clave del shell (Bash, Ksh, Zsh)            |
| **Cumple con POSIX**        | Sí                              | Sí                                 | No                                        |
| **Requiere `]` de Cierre**   | No                               | Sí (como último argumento)              | Sí (como parte de la palabra clave)                  |
| **División de Palabras**         | Sí, en variables sin comillas       | Sí, en variables sin comillas         | No, las variables se tratan como cadenas únicas |
| **Expansión de Nombres de Ruta**     | Sí, en variables sin comillas       | Sí, en variables sin comillas         | No |
| **Coincidencia de Patrones Glob** | No para igualdad de cadenas           | No para igualdad de cadenas             | Sí con el lado derecho sin comillas de `==` o `!=` |
| **Expresiones Regulares**    | No                               | No                                  | Sí con `=~` |
| **Y/O Lógicos**         | `-a`, `-o` existen pero son fáciles de malinterpretar | `-a`, `-o` existen pero son fáciles de malinterpretar | `&&`, `||` con comportamiento de cortocircuito normal |
| **Comandos Compuestos**      | Requiere llamadas `test` separadas   | Requiere llamadas `[` separadas         | Puede combinar expresiones directamente (`&&`/`||`)|
| **Poner Variables entre Comillas**       | **Obligatorio** por seguridad         | **Obligatorio** por seguridad            | Generalmente no es necesario, pero es una buena práctica |

## Cuándo Usar Cada Uno

Elegir la construcción condicional correcta depende principalmente de tus requisitos de portabilidad y la complejidad de tu lógica condicional.

### Cumplimiento de POSIX vs. Características Modernas de Bash

-   **Usa `test` o `[ ]` cuando...**
    -   **La portabilidad es primordial**: Si tu script necesita ejecutarse en cualquier shell compatible con POSIX (`sh`, `dash`, sistemas antiguos, etc.), `test` o `[ ]` son tus únicas opciones confiables.
    -   Tus condiciones son simples (verificaciones de archivos, comparaciones básicas de cadenas/enteros).
    -   Te sientes cómodo poniendo entre comillas cuidadosamente todas las variables y usando `&&`/`||` a nivel de shell fuera de los corchetes cuando necesitas lógica compuesta.

-   **Usa `[[ ]]` cuando...**
    -   **Estás escribiendo exclusivamente para Bash** (o Ksh/Zsh) y no necesitas portabilidad POSIX.
    -   Necesitas funciones avanzadas como coincidencia de patrones glob, coincidencia de expresiones regulares u operadores lógicos `&&`/`||` de estilo C.
    -   Deseas las funciones de seguridad mejoradas que evitan la división de palabras y la expansión de nombres de ruta, lo que lleva a un código más robusto y menos propenso a errores.
    -   Tus condiciones implican una lógica compleja que sería engorrosa con `test -a`/`-o`.

### Mejores Prácticas y Recomendaciones

1.  **Prioriza `[[ ]]` para Scripts de Bash**: Si tu script está destinado a Bash, `[[ ]]` es generalmente la opción preferida debido a su mayor seguridad, funcionalidad extendida y sintaxis más intuitiva para condiciones complejas. Reduce drásticamente los errores comunes de scripting relacionados con las comillas y los caracteres especiales.

2.  **Siempre Pon entre Comillas en `test` y `[ ]`**: Si *debes* usar `test` o `[ ]` para cumplir con POSIX, adquiere el hábito de **poner siempre entre comillas tus variables** para evitar comportamientos inesperados debido a la división de palabras y la expansión de nombres de ruta.

    ```bash
    # Buena práctica para [ ] y test
    VAR="una cadena con espacios"
    if [ -n "$VAR" ]; then echo "No vacío"; fi
    ```

3.  **Ten Cuidado con la Coincidencia de Patrones**: En `test` y `[ ]`, `=` se usa para la igualdad de cadenas. En `[[ ]]`, `=` y `==` pueden realizar coincidencias de patrones cuando el lado derecho no está entre comillas. Pon entre comillas el lado derecho cuando desees una comparación de cadenas literal.

4.  **Expresiones Regulares con `=~`**: Al usar `=~` en `[[ ]]`, el lado derecho generalmente no debe estar entre comillas para permitir que el shell lo interprete como un patrón de expresión regular, no como una cadena literal para coincidir.

    ```bash
    # El patrón de expresión regular sin comillas es correcto para =~ en [[ ]]
    if [[ "$LINEA" =~ ^Error: ]]; then echo "Error encontrado"; fi
    ```

## Conclusión

Usa `[ ]` o `test` cuando tu script deba ejecutarse bajo POSIX `sh`. Usa `[[ ]]` cuando tu shebang sea Bash y desees un manejo de variables más seguro, coincidencia glob, coincidencia de expresiones regulares y condiciones compuestas más limpias. El hábito principal es simple: adapta la sintaxis condicional al shell y pon entre comillas deliberadamente.