miércoles, 4 de octubre de 2017

Control de Flujo: Bucles con while / until

En el capítulo anterior, hemos desarrollado un programa basado en menús para producir varios tipos de información del sistema. El programa funciona, pero todavía tiene un problema de usabilidad significativo. Sólo ejecuta una única opción y luego termina. Peor aún, si se hace una selección no válida, el programa termina con un error, sin dar al usuario una oportunidad de probar de nuevo. Sería mejor si pudiéramos, de alguna forma, construir el programa de forma que pueda repetir la pantalla del menú y selección una y otra vez, hasta que el usuario elija salir del programa.

En este capítulo, veremos un concepto de programación llamado bucle, que puede usarse para hacer que partes de programa se repitan. El shell proporciona tres comandos compuestos para bucles. Veremos dos de ellos en este capítulo, y el tercero en un capítulo posterior.


Bucles

La vida diaria está llena de actividades repetitivas. Ir al trabajo cada día, pasear al perro, cortar zanahorias y todas las tareas que implican repetir una serie de pasos. Consideremos cortar una zanahoria en rodajas. Si expresamos esta actividad en pseudocódigo, sería algo parecido a esto:

  1. coger una tabla de cortar
  2. coger un cuchillo
  3. poner la zanahoria en la tabla de cortar
  4. levantar el cuchillo
  5. mover la zanahoria
  6. cortar la zanahoria
  7. si toda la zanahoria está cortada, parar, si no, volver al paso 4
Los pasos del 4 al 7 forman un bucle. Las acciones dentro del bucle se repiten hasta que se alcanza la condición, "toda la zanahoria está cortada".

while

bash puede expresar una idea similar. Digamos que queremos mostrar cinco números en orden secuencial desde el uno al cinco. Un script de bash podría construirse de la siguiente forma:

 #!/bin/bash


 # while-count: display a series of numbers
count=1

 while [[ $count -le 5 ]]; do
     echo $count
     count=$((count + 1))
 done
 echo "Finished."

Cuando se ejecuta, este script muestra lo siguiente:

 [me@linuxbox ~]$ while-count
 1
 2
 3
 4
 5
 Finished.

La sintaxis del comando while es:

while comandos; do comandos; done

Al igual que if, while evalúa el estado de la salida de una lista de comandos. Mientras que el estado de la salida sea cero, ejecuta los comandos dentro del bucle. En el script anterior, se crea la variable count y se le asigna un valor inicial de 1. El comando while evalúa el estado de salida el comando test. Mientras el comando testdevuelva un estado de salida cero, los comandos dentro del bucle se ejecutan. Al final de cada ciclo, se repite el comando test. Tras seis iteraciones del bucle, el valor de count se ha incrementado hasta 6, el comando test ya no devuelve un estado de salida de cero y el bucle termina. El programa continua con la siguiente línea a continuación del bucle.

Podemos usar un bucle while para mejorar el programa read-menu del capítulo anterior:

 #!/bin/bash

 # while-menu: a menu driven system information program

 DELAY=3 # Number of seconds to display results

 while [[ $REPLY != 0 ]]; do
     clear
     cat <<- _EOF_
         Please Select:

         1. Display System Information
         2. Display Disk Space
         3. Display Home Space Utilization
         0. Quit
     _EOF_
     read -p "Enter selection [0-3] > "

     if [[ $REPLY =~ ^[0-3]$ ]]; then
         if [[ $REPLY == 1 ]]; then
             echo "Hostname: $HOSTNAME"
             uptime
             sleep $DELAY
         fi
         if [[ $REPLY == 2 ]]; then
             df -h
             sleep $DELAY
         fi
         if [[ $REPLY == 3 ]]; then
             if [[ $(id -u) -eq 0 ]]; then
                 echo "Home Space Utilization (All Users)"
                 du -sh /home/*
             else
                 echo "Home Space Utilization ($USER)"
                 du -sh $HOME
             fi
             sleep $DELAY
         fi
     else
         echo "Invalid entry."
         sleep $DELAY
     fi
 done
 echo "Program terminated."

Englobando el menú en un bucle while, podemos hacer que el programa repita la pantalla de menú tras cada selección. El bucle continua mientras que REPLY no sea igual a "0" y el menú se muestra de nuevo, dando al usuario la oportunidad de realizar otra selección. Al final de cada acción, se ejecuta un comando sleep de forma que el programa espera unos segundos para permitir que el resultado de la selección se vea antes de borrar la pantalla y volver a mostrar el menú. Una vez que REPLY es igual a "0", indicando la selección "quit", termina el bucle y continua la ejecución con la siguiente línea done.

Salir de un bucle

bash proporciona dos comandos internos que pueden usarse para controlar el flujo del programa dentro de los bucles. El comando break termina un bucle inmediatamente, y el control del programa sigue con la siguiente sentencia que siga al bucle. El comando continue hace que se salte el resto del bucle, y el control del programa sigue con la siguiente iteración del bucle. Aquí vemos una versión del programa while-menuincorporando tanto break como continue:

 #!/bin/bash


 # while-menu2: a menu driven system information program

 DELAY=3 # Number of seconds to display results

 while true; do
     clear
     cat <<- _EOF_
         Please Select:

         1. Display System Information
         2. Display Disk Space
         3. Display Home Space Utilization
         0. Quit
     _EOF_
     read -p "Enter selection [0-3] > "

     if [[ $REPLY =~ ^[0-3]$ ]]; then
         if [[ $REPLY == 1 ]]; then
             echo "Hostname: $HOSTNAME"
             uptime
             sleep $DELAY
         continue
     fi
     if [[ $REPLY == 2 ]]; then
         df -h
         sleep $DELAY
         continue
     fi
     if [[ $REPLY == 3 ]]; then
         if [[ $(id -u) -eq 0 ]]; then
             echo "Home Space Utilization (All Users)"
             du -sh /home/*
         else
             echo "Home Space Utilization ($USER)"
             du -sh $HOME
         fi
         sleep $DELAY
         continue
     fi
         if [[ $REPLY == 0 ]]; then
             break
         fi
     else
         echo "Invalid entry."
         sleep $DELAY
     fi
 done
 echo "Program terminated."

En esta versión del script, configuramos un bucle sin fin (uno que nunca termina por sí sólo) usando el comando true para proporcionar un estado de salida a while. Como true siempre sale con un estado de salida de cero, el bucle nunca terminará. Esta es sorprendentemente una práctica de programación común. Como el bucle no termina por sí sólo, corresponde al programador proporcionar algún tipo de interrupción del bucle cuando haga falta. En este script, el comando break se usa para salir del bucle cuando se elige la selección "0". El comando continue se ha incluido al final de las otras opciones del script para ofrecer una ejecución más eficiente. Usando continue, el script se saltará código que no se necesita cuando se identifica una selección. Por ejemplo, si se elige la selección "1" y esta se identifica, no hay razón para probar el resto de selecciones.

until

El comando until es casi como while, excepto que en lugar de salir de un bucle cuando se encuentra un estado de salida cero, hace lo contrario. Un bucle until continua hasta que reciba un estado de salida cero. En nuestro script while-count, continuamos el bucle mientras el valor de la variable count sea menor o igual a 5. Podríamos tener el mismo resultado codificando el script con until:

 #!/bin/bash


 # until-count: display a series of numbers
    count=1

 until [[ $count -gt 5 ]]; do
     echo $count
     count=$((count + 1))
 done
 echo "Finished."

Cambiando la expresión test a $count -gt 5, until, terminaremos el bucle en el momento correcto. La decisión de usar el bucle while o until consiste normalmente en elegir el que permita escribir el test más claro.

Leyendo archivos con bucles

while y until pueden procesar entrada estándar. Esto permite que los archivos sean procesados con bucles while y until. En el siguiente ejemplo, mostraremos el contenido del archivo distros.txt usado en los capítulos anteriores:

 #!/bin/bash


 # while-read: read lines from a file

 while read distro version release; do
     printf "Distro: %s\tVersion: %s\tReleased: %s\n" \
     $distro \
     $version \
     $release
 done < distros.txt

Para redireccionar un archivo al bucle, colocamos el operador de redirección tras la sentencia done. El bucle usara read para introducir los campos desde el archivo redirigido. El comando read saldrá cada vez que lea una línea, con un estado de salida cero hasta que se alcance el final del archivo. En este punto, saldrá con un estado de salido no-cero, terminando de esta forma el loop. También es posible entubar la entrada estándar dentro de un bucle:

#!/bin/bash

# while-read2: read lines from a file

sort -k 1,1 -k 2n distros.txt | while read distro version release; do

    printf "Distro: %s\tVersion: %s\tReleased: %s\n" \
    $distro \
    $version \
    $release
done

Aquí hemos tomado la salida del comando sort y hemos mostrado la cadena de texto. Sin embargo es importante recordar que como un entubado ejecutará el bucle en un subshell, cualquier variable creada o asignada dentro del bucle se perderá cuando termine el bucle.

Resumiendo

Con la introducción de los bucles, y nuestros anteriores encuentros con las ramificaciones, las subrutinas y secuencias, hemos visto la mayoría de los tipos de control de flujo usados en programas. bash se guarda algunos trucos en la manga, pero son refinamientos de estos conceptos básicos.

Para saber más


No hay comentarios:

Publicar un comentario

Nota: solo los miembros de este blog pueden publicar comentarios.