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:
- coger una tabla de cortar
- coger un cuchillo
- poner la zanahoria en la tabla de cortar
- levantar el cuchillo
- mover la zanahoria
- cortar la zanahoria
- si toda la zanahoria está cortada, parar, si no, volver al paso 4
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
count=$((count + 1))
done
echo "Finished."
Cuando se ejecuta, este script muestra lo siguiente:
[me@linuxbox ~]$ while-count
1
2
3
4
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
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.
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
- La Guía de Bash para Principiantes del Proyecto de Documentación Linux tiene algunos ejemplos más de bucles con while:
http://tldp.org/LDP/Bash-Beginners-Guide/html/sect_09_02.html
- La Wikipedia tiene un artículo sobre bucles, que es parte de un artículo mayor sobre control de flujo:
http://en.wikipedia.org/wiki/Control_flow#Loops
No hay comentarios:
Publicar un comentario
Nota: solo los miembros de este blog pueden publicar comentarios.