educación, informática y demás

24.13 - Repaso general

Sentencia condicional de selección

Aunque el título sea muy rimbombante, en realidad estamos hablando de la sentencia IF o S.

En bash tenemos disponible un comando para utilizar la selección, el comando if.

Según la ayuda del comando if tenemos tres formas de utilizarlo, en función de las partes opcionales.

  1. if COMANDO… then… fi. Si se ejecuta el COMANDO sin errores, entonces se realiza una acción.
  2. if COMANDO then … else…fi. Si se ejecuta el COMANDO sin errores entonces realiza una acción sino realiza otra acción.
  3. if COMANDO1 then ACCION1 elif COMANDO2 then ACCION2 [elif … else … ] fi. Si se ejecuta el COMANDO1 sin errores, entonces se realiza ACCION1. Si no, entonces si se ejecuta el COMANDO2 sin errores se realizar ACCION 2, sino si…. finalmente, si queremos, podemos podemos utilizar un else que permita ejecutar una ACCION_FINAL si no se ejecuta sin problemas ninguno de los comandos utilizados en los anteriores if elif.

Una diferencia importante de este comando if con respecto a la sentencia if de los lenguajes de programación de alto nivel como pyton o php es que la condición de este comando se comprueba ejecutando un comando.

Si el comando que hemos puesto en la condición se ejecuta sin errores, entonces se cumple la condición. Sino, es decir si el comando que hemos puesto en la condición retorna un código de error, entonces no se cumple la condición.

Esta es, quizá, la parte más complicada de utilizar el comando if. Estamos acostumbrados, si programamos en otros lenguajes de programación, a especificar una condición lógica, por ejemplo usuario == «» o edad >= 18. No obstante, en bash lo que escribimos como condición es un comando.

La mejor forma de interiorizar esto es realizar una serie de ejemplos.

Ejemplos

Ejemplo 1

Ejemplo 2

Si NO funciona correctamente el comando ping -c2 $server entonces ejecuta la línea 7.

Hemos cambiado los mensajes de salida para que se adapten a la nueva función del script, realizar alguna acción si no tenemos conexión.

Si lo ejecutamos y hay conexión, no mostrará ninguno de los mensajes que hemos escrito.

Vamos a añadir algo…

Veamos el comando que estamos ejecutando ahora.

La diferencia entre la primera ejecución y la segunda es que la salida del comando ping que estamos utilizando como condición, tanto la salida estándar como la de errores, la estamos redirigiendo al fichero /dev/null. Por tanto, este comando no va a mostrar nada en pantalla.

El fichero /dev/null es un fichero especial del sistema que no almacena nada, es como una papelera o agujero negro. Todo lo que escribamos ahí no se almacenará, se pierde.

Vamos a comprobar que pasa si no tenemos conexión. Desconecto el cable de red, en mi caso lo configuro en la máquina virtual 🙂

El icono de la barra de notificaciones me indica que no tengo red.

Ahora ejecutamos

Sigo con el cable de red desconectado.

Vamos a analizar una de las líneas que hemos modificado.

Si analizamos por partes, el comando echo mostrará en la salida estándar un mensaje. A continuación, hay una doble redirección que redirige la salida estándar a un fichero. Por tanto, ya no se mostrará el mensaje en pantalla, que es la salida estándar por defecto, sino que se almacenará en el fichero cuya ruta aparece a continuación que es ./network.log

Como es doble redirección, si el fichero existiera en el momento de ejecutarse el comando, la salida se añadirá al final del fichero. Si no existe, se crea un fichero nuevo y se escribe la salida en él. No se sobrescriben datos.

Si lo analizo en conjunto esta línea almacena un mensaje al final de un fichero. Si analizo todo el script en conjunto, almacenará una serie de mensajes de advertencia en un fichero de log si no hay conexión con el equipo almacenado en $server.

Vamos a añadir otro cambio al código, y seguimos desconectados de la red.

Un par de detalles, la ruta del fichero de log es una ruta relativa, está en el directorio actual, por eso comienza por ./ y no por /. Si comenzara por / sería una ruta absoluta que comenzaría en raíz.

El cambio introducido es utilizar una variable para almacenar la ruta del fichero de log. De esta forma es más sencillo razonar con el código fuente, puesto que no estamos viendo todo el tiempo la ruta del fichero en las redirecciones y, además, facilita la modificación del código puesto que si queremos cambiar la ruta del fichero de log solo tenemos que hacerlo en un sitio, en la línea 6 que es dónde se asigna la ruta del fichero a la variable logFile.

Vamos a añadir otro cambio más

El cambio está en la línea 5, dónde ahora asignamos a la variable server el valor del primer parámetro pasado al script al ejecutarlo. De esta forma, podemos decidir la dirección del host para el que vamos a comprobar si hay conexión justo en el momento de ejecutar el script.

Deberíamos comprobar que se pasa algún valor en el primer parámetro. Pero antes, vamos a añadir otra modificación.

Vamos a añadir otro cambio…

Hemos añadido tres líneas nuevas: 8, 9 y 10. Se trata de un nuevo comando if que ejecuta el comando test pasándole como parámetros -z y $server.

El comando test permite comprobar y comparar valores de cadenas de texto, de números enteros e información de ficheros.

Según la página de manual de test, éste espera una EXPRESIÓN.

Expresión puede ser una de varias opciones… vamos a lo que nos interesa, que es la que hemos usado.

Es decir la expresión -z $server se cumplirá si $server está vacío. Si es así, el comando test devuelve un código de retorno de éxito o todo correcto, es decir se ejecuta correctamente y se cumplirá la condición en la que lo estamos usando.

¿Qué significa que $server esté vacío? Pues la variable server contiene el valor del primer parámetro, si $server no contiene nada, significa que $1 tampoco contenía nada y, por tanto, no se ha pasado ningún parámetro al script.

De esta forma, si server está vacío, que no debería estarlo, lo que hacemos es que le asignamos un valor por defecto. Eso es lo que hace este código.

Vamos a ver qué se ha almacenado en el fichero de log.

Vamos a añadir otro cambio…

La condición del if es la misma, si server está vacío entonces… no obstante hemos cambiado la acción a realizar: advertimos del problema y terminamos la ejecución del script con el comando exit retornando un 1 como código de salida del script. Este código de salida del script indica que ha habido un error. Se considera que un programa ha terminado correctamente si retorna un código de salida con valor 0. En cualquier otro caso, significa que ha habido un problema.

El script termina la ejecución al ejecutar el comando exit 1 en la línea 10.

Vamos a añadir otro cambio, que no altera la función del script, pero facilita, creo, su comprensión.

Volvamos un momento a la página de manual de test.

Es igual, solo que la forma de escribirlo nos recuerda más a un lenguaje de programación de alto nivel (LPAN de aquí en adelante)

Ejecutan el mismo comando, el comando test, con la misma expresión.

Visualmente, la opción de los corchetes recuerda más a los LPAN y nos facilita pensar en la condición como una condición lógica, si está vacía la variable $server entonces…

Podemos utilizar cualquiera de las dos opciones. Eso sí, no olvides nunca que se trata de un comando.

La condición de un if o un elif es el código de salida de laejecución de un comando.

Vamos a darle otra vuelta más al script, la versión semi pro.

El cambio que hemos hecho es que hemos añadido un bucle for que utiliza la variable server para recorrer la lista de parámetros pasados al script.

De esta forma, el bucle for ejecutará para cada valor pasado por parámetro la comprobación de conexión que está en el cuerpo del bucle.

Si nos fijamos, el cuerpo del bucle contiene exactamente los mismos comandos que teníamos antes.

Ejemplo 2

Lo primero que deberíamos hacer es comprobar que hace el comando test con la expresión que se está utilizando en el código: $nota -gt 5.

Según esta información y la expresión, el comando test se ejecutará con éxito si $nota es mayor que 5. Así que, tendremos que modificar la condición para que se ajuste a la realidad, salvo que el aprobado esté por encima del cinco, que no es el caso.

Si la nota es mayor o igual que cinco muestra un mensaje, sino muestra otro distinto. Estamos usando la opción Si…ENTONCES…SINO o if … then … else .. fi

Vamos a modificarlo un poco

El uso de elif facilita comprobaciones en cascada. Si no tuvieramos la opción de elif, y solo usaramos if then else, el código quedaría así:

Vamos a darle otra vueltita

Si no tuvieramos el elif quedaría así

Vamos a recibir la nota en el primer parámetro

Veamos cómo funciona.

Pero, vamos a probar a ver qué pasa si le pasamos parámetros que no sean lógicos, aunque si se puedan pasar y si no le pasamos ningún parámetro.

Vamos a modificar el código de nuestro script para controlar estos problemas debidos a los datos que nos pasa el usuario por parámetro en tiempo de ejecución

Tiempo de ejecución: cuando se está ejecutando el script, dónde no tenemos control sobre que datos nos pasarán en la entrada, así que tenemos que insertar un control de errores que evite estas situaciones de error en la ejecución del script.

  • Vamos a empezar controlando que nos pasen un parámetro.
  • Ahora que el parámetro sea un valor numérico. test -lt 0 –> ERROR –> No es un número
  • Después que el parámetro pasado esté entre 0 y 10.
  • Por último mostramos la calificación cualitativa

¿Cómo comprobamos que valor que nos han pasado es un entero?

Cualquier número entero debe ser igual a él mismo. Si ejecutamos test comprobando si l valor de una variable es igual a si mismo y da error, significa que esa variable no contiene un número entero.

En la línea 10 hemos utilizado el comando test igual que hicimos en un ejemplo anterior con el comando ping. Si el comando test produce un error es porque el valor que contiene nota no es un entero, porque tenemos clarísimo que cualquier número entero es igual a sí mismo.

Vamos a probarlo.

Nos damos cuenta que estamos repitiendo una y otra vez un echo seguido de un exit 1 para terminar la ejecución del script en caso de error Vamos a crear una función error que reciba por parámetro un mensaje de error. La función mostrará el mensaje de error, precedido por una etiqueta «[ERROR] – » y terminará la ejecución del script con código de salida 1.

Usaremos esta función cada vez que queramos mostrar un error crítico: que no permite continuar con la ejecución del script.

Vamos a modificar el script para que muestre la nota cuantitativa y después la nota cualitativa. Además, recibirá una lista variable de notas por parámetro.

  • Para recorrer con una lista de parámetros usamos for.
  • El el cuerpo del for, realizaremos las acciones pertinentes con el parámetro con el que estemos trabajando en ese momento, que estará guardado en la variable del for.

Si no recordamos la sintaxis, help for

Una solución sería la siguiente

La comprobación de valores para nota incorrectos se debe hacer dentro del bucle y no hace falta comprobar que se haya pasado algún valor por parámetro puesto que si no se pasa ningún valor nunca se ejecutará el bucle.

Vamos a comprobar cómo se ejecuta

¿Cómo podemos solucionarlo para que si la nota no cumple los requisitos muestre un mensaje de advertencia pero no termine la ejecución del script, que continúe con el siguiente valor.

Vale, pero si nos fijamos en el código razonar sobre el bucle es un poco complejo porque el cuerpo del bucle, que realiza una única acción, es demasiado grande.

Podemos solucionarlo utilizando una función llamada procesaNota que reciba como parámetro una nota.

La función recibe en el primer parámetro el valor de la nota y después, dependiendo del valor de nota, realizará una cosa u otra.

El bucle lo único que tiene que hacer es recorrer cada parámetro pasado al script y ejecutar la función procesaNota pasándole por parámetro el valor de la variable con la que está recorriendo los parámetros del script.

Vamos a probarlo…

Ejemplo 3

Vamos a crear un script que se encargue de crear una cuenta de usuario si no existe en el sistema. La cuenta de usuario utilizará /bin/bash como shell, crearemos su directorio personal dentro de /home y le asignaremos como contraseña «ClaveRoot#20».

Dejar una respuesta