Condicionales en Bash

Este es uno de los capítulos del tutorial Scripts en Bash. Encontrarás los enlaces a todos los de capítulos, al final de este artículo.

Como te comenté en el capítulo anterior sobre variables de este tutorial sobre scripting en Bash, hasta el momento solo has podido hacer scripts lineales. Es decir, tu script empezará en un punto y terminará, pero no puedes hacer nada por variar el flujo de tu script en función de lo que allí suceda. Sin embargo, en este nuevo capítulo del tutorial puedes cambiar esta situación. Así, si al realizar una operación obtienes un resultado u otro, podrás tomar una decisión para emprender una u otra acción. Así en este capítulo verás los condicionales en Bash.

Con los condicionales en Bash, ya podemos, realmente, empezar a profundizar en el maravilloso mundo del scripting, y un poquito en la automatización. Por ejemplo, puedes crear un proceso que una vez al mes te borre los archivos de un directorio si el espacio es superior a uno determinado y si la fecha del archivo es inferior a una dada. Todo está condicionado…

Condicionales en Bash

Condicionales en Bash

Si tienes alguna idea de programación seguro que has oído hablar de los condicionales. Si no, es posible que conozcas el servicio ifttt. Eso de que si pasa esto entonces haz aquello. Pues ese servicio no es mas que una extrapolación de esto de los condicionales. Se trata de realizar una u otra acción en función de una pregunta. Si el resultado de la pregunta es uno, emprenderás una acción y si el resultado es otro, emprenderás una acción diferente.

En Bash, esto de los condicionales se materializa con if then else y con case. Pero independientemente de uno u otro, en cualquier caso, se parte de resolver una cuestión, una prueba, un test, al final una comparación. De esta manera esto es lo primero que tienes que abordar en este capítulo, comparaciones en Bash.

Comparando en Bash

Para comparar en Bash se utiliza test o [. Ambos son totalmente equivalentes, y ambos están implementados, en el propio Bash. En el caso [, por cuestiones prácticas es obligatorio también utilizar su pareja [ o de otra forma te da error. Esta es la razón para que tenga que estar separado el corchete de la expresión que viene a continuación, porque es un comando.

Por otro lado, también puedes utilizar dobles corchetes [[, para realizar tus comparaciones. Los dobles corchetes resultan ser una mejora respecto a los simples. Así, las diferencias entre uno y otro son las siguientes,

  1. No tienes que utilizar las comillas con las variables, los dobles corchetes trabajan perfectamente con los espacios. Así [ -f "$file" ] es equivalente a [[ -f $file ]].
  2. Con [[ puedes utilizar los operadores || y && , así como para las comparaciones de cadena.
  3. Puedes utilizar el operador =~ para expresiones regulares, compo por ejemplo [[ $respuesta =~ ^s(i)?$ ]]
  4. También puedes utilizar comodines como por ejemplo en la expresión [[ abc = a\* ]]

Es posible que te preguntes por la razón para seguir utilizando [ simple corchete en lugar de doble. La cuestión es por compatibilidad. Si utilizas Bash en diferentes equipos es posible que te encuentres alguna imcompatibilidad. Así que depende3 de ti y de donde lo vayas a utilizar. A modo de resumen, aquí tienes diferentes opciones,

Comparando cadenas

[[[
>>
<<
==
!=!=

Comparando enteros

[[[
-gt-gtmayor
-lt-ltmenor
-ge-gemayor o igual
-le-lemenor o igual
-eq-eqigual
-ne-nedistinto

Aquí quiero que te des cuenta que [[ 001 = 1 ]] es falso mientras que [[ 001 -eq 1 ]] es cierto.

Operadores booleanos

[[[
&&-a
||-o

Agrupación

Para agrupar operaciones booleanas puedes utilizar paréntesis con los dobles corchetes, mientras que en el caso de los simples corchetes deberás utilizar los paréntesis pero escapados.

Otros comparadores interesantes

  • -d te permitirá saber si es un directorio y si existe
  • -f lo mismo que en el caso anterior pero para archivos
  • -r en este caso te permite saber si el archivo tiene permiso de lectura
  • -s con esta opción puedes saber si el tamaño del archivo es mayor que cero. Es decir, que no se trata de un archivo vacío
  • -w te permitirá identificar si el archivo tienen permisos de escritura
  • -x lo mismo que en el caso anterior pero para el caso de permisos de ejecución.

Condicionales: if then else

Hemos llegado al punto de que hagas tu primera comparación. Para esto tienes que utilizar if then else. Vamos, si esto se cumple haz aquello y si no, pues haz otra cosa. Dicho así suena fácil, y realmente lo es. De esta forma. Vamos a establecer una variable a y le vas a asignar un nombre. Si este nombre termina en a supondremos que es un nombre de mujer, y si termina con cualquier otra letra supondremos que es de hombre. Como viste en el capítulo anterior del podcast, para utilizar argumentos, son los $1 a $9. De esta manera nuestro script tendrá un aspecto como lo que te indico a continuación,

#!/bin/bash

if [[ $1 =~ (.*)a$ ]]
then
    echo Sra. $1
else
    echo Sr. $1
fi

De esta forma aprovecho las características que ofrece los dobles corchetes con las expresiones regulares. No es por complicar la cosa. Otro ejemplo, es para saber si un número es par o impar. Para ello, puedes hacer un script como el que te muestro a continuación,

#!/bin/bash

if [[ $(($1 % 2)) == 0 ]]
then
    echo Par
else
    echo Impar
fi

Si te fijas he añadido una sangría, de forma que tanto echo Par como echo Impar están desplazados hacia la derecha. Esto es mas por cuestiones de legibilidad que por otra razón. No es necesario hacer esto en Bash. Perfectamente lo podrías haber puesto como,

#!/bin/bash

if [[ $(($1 % 2)) == 0 ]]
then
echo Par
else
echo Impar
fi  

Sin embargo, coincidirás conmigo, que esta segunda forma, es bastante mas difícil de leer que en el caso del primer ejemplo.

Algunas opciones con if-then-else

Indicarte que puedes utilizar diferentes opciones, dependiendo del objetivo que persigas. Así, puedes hacer,

si se cumple algo entonces haz esto

if [[ $a > $b ]]
then
    echo mayor
fi

si se cumple algo entonces haz esto en caso contrario haz lo otro

if [[ $a > $b ]]
then
    echo mayor
else
    echo menor
fi

si se cumple algo entonces haz esto en caso contrario si se cumple otra cosa haz lo otro

if [[ $a > $b ]]
then
    echo mayor
elif [[ $b > $c ]]
then
    echo lo que sea
fi

Y por supuesto una que combina las anteriores porque contiene todas las posibilidades, y que me niego a transcribir, porque seguro que me equivoco.

if [[ $a > $b ]]
then
    echo "mayor"
elif [[ $b > $c ]]
then
    echo "lo que sea"
else
    echo "nada de lo anterior"
fi

if-then anidados

Por supuesto, es posible el uso de if-then anidados. Un if-then anidado es como una especie de yincana (no tenía ni idea que se escribiera así). Me refiero a ese tipo de competiciones que están compuestas de varias pruebas, de forma que cada vez que superas una, tienes una siguiente prueba que superar. Pues esto es lo mismo. Un if-then anidado, no es mas que una sucesión de comparaciones, de forma que cada vez que superas uno te tienes, que enfrentar a un siguiente. Por ejemplo

if [[ $a -gt $b ]];then
    if [[ $b -gt $c ]]; then
        if [[ $c -gt $d ]]; then
            if [[ $d -gt $e ]];then
                if [[ $e -gt $f ]]; then
                    echo "$a es muy grande"
                fi
            fi
        fi
    fi
fi

Concatenando comparaciones

En ocasiones necesitarás que se cumplan varias condiciones o una combinación de ellas para poder tomar una u otra acción. Para conseguir esta concatenación de operaciones, tienes que utilizar operadores booleanos,

  • y, representado por &&. Esto implica que las dos comparaciones tienen que ser ciertas.
  • o, representado por ||. Con que una de las dos comparaciones sea cierta es suficiente.

Por ejemplo, si quieres comprobar que un fichero es de lectura y escritura, tienes que utilizar [[ -r $f ]] && [[ -w $f ]]. Ahora bien, si te vale con que sea de lectura o escritura, tienes que tuilizar el operador || quedando en esta ocasión [[ -r $f ]] || [[ -w $f ]]

Condicionales: Case

Un caso especial es cuando quieres comparar un elemento con diferentes supuestos. Podrías hacer if-then concatenados como has visto anteriormente, sin embargo, lo que no me puedes negar es que las concatenaciones son difíciles de leer. Al final se convierten el algo realmente farragoso, no solo de leer, si no también de trabajar. En algunos casos puedes reemplazar estas concatenaciones por otro tipo de comparación como son los Case. Lo que sin duda, Case traerá a tu código mucha mas claridad, según el caso, y resultará mas cómodo leer el código posteriormente.

Así la sintasis de los Case es como puedes ver a continuación,

case <expresión> in
    <patrón 1>)
        comandos
        ;;
    <patrón 2>)
        comandos
        ;;
    *)
        comandos
        ;;
esac

Puedes utilizarla para operaciones realmente sencillas, como podría ser el ejemplo, sobre el significado de los colores que lees a continuación,

#!/bin/bash
case $1 in
    amarillo)
        echo "optimismo, claridad, calor"
        ;;
    naranja)
        echo "amigable, social, seguridad"
        ;;
    rojo)
        echo "atrevido, excitación, joven"
        ;;
    violeta)
        echo "creatividad, imaginativo, sabio"
        ;;
    azul)
        echo "útil, fuerza, fiel"
        ;;
    verde)
        echo "paz, salud, crecimiento"
        ;;
    blanco)
        echo "equilibrio, tranquilidad, neutro"
        ;;
    *)
        echo "Lo siento, no conozco ese color"
        ;;
esac    

No solo lo puedes utilizar en este caso, también puedes complicarlo, añadiendo rangos, un ejemplo,

#!/bin/bash
case $1 in
    [0-9])
        echo "Unidades"
        ;;
    1[0-9]|2[0-9]|3[0-9]|4[0-9]|5[0-9]|6[0-9]|7[0-9]|8[0-9]|9[0-9])
        echo "Decenas"
        ;;
    1[0-9][0-9]|2[0-9][0-9]|3[0-9][0-9]|4[0-9][0-9]|5[0-9][0-9]|6[0-9][0-9]|7[0-9][0-9]|8[0-9][0-9]|9[0-9][0-9])
        echo "Centenas"
        ;;
    1[0-9][0-9][0-9]|2[0-9][0-9][0-9]|3[0-9][0-9][0-9]|4[0-9][0-9][0-9]|5[0-9][0-9][0-9]|6[0-9][0-9][0-9]|7[0-9][0-9][0-9]|8[0-9][0-9][0-9]|9[0-9][0-9][0-9])
        echo "Millares"
        ;;
    *)
        echo "Ese es un número muy grande"
        ;;
esac

Probablemente te preguntarás que forma mas extraña de poner rangos. La cuestión es que no estamos poniendo rangos numéricos, sino que son rango de caracteres, es decir, caracteres que van del 0 al 9. En este caso, probablemente sea mejor utilizar if-then,

#!/bin/bash
if [[ $1 -lt 10 ]]
then
    echo "Unidades"
elif [[ $1 -lt 100 ]]
then
    echo "Decenas"
elif [[ $1 -lt 1000 ]]
then
    echo "Centenas"
elif [[ $1 -lt 10000 ]]
then
    echo "Millares"
else
    echo "Un número muy grande"
fi

Pero puedes pervertir el uso de Case como tu quieras. Así puedes simplificar el ejemplo anterior, utilizando otra opción con Case,

#!/bin/bash
case 1 in
    $(($1 <= 10)))
        echo "Unidades"
        ;;
    $(($1 <= 100)))
        echo "Decenas"
        ;;
    $(($1 <= 1000)))
        echo "Centenas"
        ;;
    $(($1 <= 10000)))
        echo "Millares"
        ;;
    *)
        echo "Un número muy grande"
        ;;
esac

Como puedes ver, así que resulta mucho mas sencillo de leer.

Para terminar, otro ejemplo, donde puedes establecer la periodificación de la historia de forma relativamente sencilla utilizando un Case,

#!/bin/bash

case 1 in
    $(($1 <= -125000))*)
        periodo="Paleolítico inferior"
        ;;
    $((-125000 < $1 &&  $1 <= -35000))*)
        periodo="Paleolítico medio"
        ;;
    $((-35000 < $1 &&  $1 <= -10000))*)
        periodo="Paleolítico superior"
        ;;
    $((-10000 < $1 &&  $1 <= -5500))*)
        periodo="Mesolítico"
        ;;
    $((-5500 < $1 &&  $1 <= -4000))*)
        periodo="Neolítico"
        ;;
    $((-4000 < $1 &&  $1 <= -3000))*)
        periodo="Edad del Cobre"
        ;;
    $((-3000 < $1 &&  $1 <= -2000))*)
        periodo="Edad del Bronce"
        ;;
    $((-2000 < $1 &&  $1 <= -1000))*)
        periodo="Edad del Hierro"
        ;;
    *)
        periodo="Historia"
        ;;
esac
echo "Te encuentras en el $periodo"

Conclusiones

Con este segundo capítulo del tutorial sobre scripts en Bash, ya conoces las variables y los condicionales, tanto if then ese como su primo Case. Lo siguiente es conocer el funcionamiento de los bucles, para evitar tener que realizar operaciones repetitivas.


Más información,

9 comentarios en “Condicionales en Bash

    1. AT
      atareao hace 3 años

      Hola Eliot,
      Muchas gracias por la corrección, ya está modificado.
      Un saludo y gracias.

  1. DA
    Danburgues hace 3 años

    Saludos, no se que hago mal
    Porque me entra en el primer condicional en lugar del segundo?

    sma@sma-hpstream11notebookpc:~/Scripts de prueba$ cat nuevaprueba.sh
    #! /bin/bash

    OPER=»r»

    if [[ $OPER==»s» ]]
    then
    echo oper es s — $OPER
    elif [[ $OPER==»r» ]]
    then
    echo oper es r — $OPER
    else
    echo oper es distinta —$OPER
    fi
    echo script finalizado
    exit 0
    sma@sma-hpstream11notebookpc:~/Scripts de prueba$ bash nuevaprueba.sh
    oper es s — r
    script finalizado

    1. JO
      Joan hace 2 años

      Hola Danburgues,

      Tal como escribes el código, en efecto entras en el primer bucle, pues cuando entras en el primer if,
      [[ $OPER==»s» ]] al no poner espacio a ambos lados del ==, el contenido sería [[ «r»==»s» ]] y esto constituye una única expresión de caracteres en lugar de una comparación, lo cual siempre será cierto, pues la expresión no está vacía.
      Si ejecutas bash -x nuevaprueba.sh, seguro que lo verás más claro.

      La solución es poner espacios entre ==, es decir [[ $OPER == «s» ]] y ya entrará en el segundo if.

      Espero que esto te ayude un poco.

      Saludos

  2. LU
    Luis Andrés Villalobos Rodríguez hace 2 años

    Hola en el ejercicio if para ver si un numero es par o impar no me funciona me sale como si no leyera el then y pasara directo haciendo que todo numero me de impar, porfa no se si podrías ayudarme con eso hace rato lo estoy intentando soy novato en esto y no he podido hallar el error.

  3. FE
    Fernando Arellano hace 2 años

    Muchas gracias por la aportación, gracias a esta publicación pude avanzar. Saludos.

  4. JE
    jenrry hace 2 años

    hola porfavor tu ayuda es un script que lee los nombres de archivos pdf algunos tiene espacios entre sus nombres y otros tienen el nombre todo junto sin espacios en sus nombres, pero cuando lo entrecomillo igual los toma como si fueran varios archivos,
    #!/bin/bash
    echo «Este script junta varios pdf en uno»
    echo «Ingrese los nombres de los pdf uno por uno»
    read -a pdf1
    read -a pdf2
    read -a pdf3
    echo «Ingresa un nombre para del pdf final»
    read nombrefinal
    echo «Procesando»
    echo «!Listoo!»
    pdftk «$pdf1» «$pdf2» «$pdf3» cat output «$nombrefinal»
    echo «!Listoo!»

  5. BO
    bob hace 1 año

    hola, como hago para poner simbolos en los case como +,*,/,- ?

Deja una respuesta

Tu dirección de correo electrónico no será publicada. Los campos obligatorios están marcados con *