miércoles, 4 de octubre de 2017

Control de flujo: Bucles con for

En este último capítulo sobre control de flujo, veremos otra de las estructuras de bucles del shell. El bucle for difiere de los bucles while y until en que ofrece un medio de procesado de secuencias durante un bucle. Esto se vuelve muy útil cuando estamos programando. Por consiguiente, el bucle for es una estructura muy popular en scripting de bash.

Un bucle for se implementa, básicamente , con el comando for. En versiones modernas de bash, for está disponible en dos formas.

for: Forma tradicional del shell

La sintaxis original del comando for es:

 for variable [in palabras]; do
     comandos
 done

Donde variable es el nombre de la variable que se incrementará durante la ejecución del bucle, palabras es una lista opcional de elementos que se asignarán secuencialmente a variable, y comandos son los comandos que se van a ejecutar en cada iteración del bucle.

El comando for es útil en la línea de comandos. Podemos comprobar fácilmente cómo funciona:

 [me@linuxbox ~]$ for i in A B C D; do echo $i; done
 A
 B
 C
 D

En este ejemplo, se le da a for una lista de cuatro palabras: "A", "B", "C" y "D". Con una lista de cuatro palabras, el bucle se ejecuta cuatro veces. Cada vez que se ejecuta el bucle, se asigna una palabra a la variable i, Dentro del bucle, tenemos un comando echo que muestra el valor de i para mostrar la asignación. Igual que los bucles while y until, la palabra done cierra el bucle.

La característica realmente potente de for es el número de formas interesantes de crear listas de palabras. Por ejemplo, a través de expansión con llaves:

 [me@linuxbox ~]$ for i in {A..D}; do echo $i; done
 A
 B
 C
 D

or expansión de rutas:

 [me@linuxbox ~]$ for i in distros*.txt; do echo $i; done
 distros-by-date.txt
 distros-dates.txt
 distros-key-names.txt
 distros-key-vernums.txt
 distros-names.txt
 distros.txt
 distros-vernums.txt
 distros-versions.txt

o sustitución de comandos:

 #!/bin/bash


 # longest-word : find longest string in a file

 while [[ -n $1 ]]; do
     if [[ -r $1 ]]; then
         max_word=
         max_len=0
         for i in $(strings $1); do
             len=$(echo $i | wc -c)
             if (( len > max_len )); then
                 max_len=$len
                 max_word=$i
             fi
         done
         echo "$1: '$max_word' ($max_len characters)"
     fi
     shift
 done

En este ejemplo, buscamos la cadena más larga dentro de una archivo. Una vez que le damos uno o más nombres de archivo en la línea de comandos, este programa usa el programa strings (que está incluido en el paquete GNU binutils) para generar una lista de "words" en texto legible en cada archivo. El bucle for procesa cada palabra por turnos y determina si la palabra actual es la más larga encontrada hasta ahora. Cuando concluye el bucle, se muestra la palabra más larga.

Si la porción opcional in words del comando for se omite, for por defecto procesa los parámetros posicionales. Modificaremos nuestro script longest-word para usar este método:

 #!/bin/bash

 # longest-word2 : find longest string in a file

 for i; do
     if [[ -r $i ]]; then
         max_word=
         max_len=0
         for j in $(strings $i); do
             len=$(echo $j | wc -c)
             if (( len > max_len )); then
                 max_len=$len
                 max_word=$j
             fi
         done
         echo "$i: '$max_word' ($max_len characters)"
     fi
 done

Como podemos ver, hemos cambiado el loop más externo para usar for en lugar de while. Omitiendo la lista de palabras en el comando for, se usan los parámetros posicionales en su lugar. Dentro del bucle, las instancias previas de la variable i han sido cambiadas a la variable j. El uso de shift también ha sido eliminado.

¿Por qué i?

Habrás notado que se ha elegido la variable i para cada uno de los ejemplos del bucle for anteriores. ¿Por qué? No hay una razón específica en realidad, sólo la tradición. La variable utilizada con for puede ser cualquier variable válida, pero i es la más común, seguida de j y k.

El origen de esta tradición viene del lenguaje de programación Fortran. En Fortran, las variables no declaradas que comiencen con las letras I, J, K, L y M son automáticamente tratadas como enteros, mientras que las variables que comiencen con cualquier otra letra se tratan com reales (números con fracciones decimales). Este comportamiento llevó a los programadores a usar las variables I, J y K para variables de bucles, ya que era menos trabajoso usarlas cuando se necesitaba una variable temporal (y las variables de bucles lo son a menudo).

También llevó al siguiente chiste basado en Fortran:

"DIOS es real, a menos que se le declare como entero".


for: Forma del lenguaje C

Versiones recientes de bash han añadido una segunda forma de la sintaxis del comando for, una que se parece a la forma que encontramos en el lenguaje de programación C. Muchos otros lenguajes soportan esta forma también:

 for (( expresión1; expresión2; expresión3 )); do
     comandos
 done

donde expresión1, expresión2 y expresión3 son expresiones aritméticas y comandos son los comandos a ejecutar durante cada iteración del bucle.

En términos de comportamiento, esta forma es equivalente a la siguiente estructura:

 (( expresión1 ))
 while (( expresión2 )); do
     comandos
     (( expresión3 ))
 done

expresión1 se usa para inicializar condiciones para el bucle, expresión2 se usa para determinar cuando termina el bucle y expresión3 se ejecuta al funal de cada iteracion del bucle.

Aquí tenemos una aplicación típica:

 #!/bin/bash


 # simple_counter : demo of C style for command

 for (( i=0; i<5; i=i+1 )); do
     echo $i
 done

Cuando lo ejecutamos, produce la siguiente salida:

 [me@linuxbox ~]$ simple_counter
 0
 1
 2
 3
 4

En este ejemplo, expresion1 inicicaliza la variable i con el valor cero, expresión2permite al bucle continuar mientras que el valor de i permanezca menor que 5, y expresión3 incrementa el valor de i en uno cada vez que se repite el bucle.

La forma de lenguaje C de for es útil siempre que necesitemos una secuencia numérica. Veremos varias aplicaciones de esto en los siguientes dos capítulos.

Resumiendo

Con nuestro conocimiento del comando for, aplicaremos ahora las mejoras finales a nuestro script sys_info_page. Actualmente, la función report_home_space aparece así:

 report_home_space () {
     if [[ $(id -u) -eq 0 ]]; then
         cat <<- _EOF_
             <H2>Home Space Utilization (All Users)</H2>
             <PRE>$(du -sh /home/*)</PRE>
             _EOF_
     else
         cat <<- _EOF_
             <H2>Home Space Utilization ($USER)</H2>
             <PRE>$(du -sh $HOME)</PRE>
             _EOF_
     fi
     return
 }

A continuación, la reescribiremos para proporcionar más detalle para cada directorio home de usuario, e incluiremos el número total de archivos y subdirectorios en cada uno de ellos:

 report_home_space () {
     local format="%8s%10s%10s\n"
     local i dir_list total_files total_dirs total_size user_name


     if [[ $(id -u) -eq 0 ]]; then
         dir_list=/home/*
         user_name="All Users"
     else
         dir_list=$HOME
         user_name=$USER
     fi
     echo "<H2>Home Space Utilization ($user_name)</H2>"

     for i in $dir_list; do

         total_files=$(find $i -type f | wc -l)
         total_dirs=$(find $i -type d | wc -l)
         total_size=$(du -sh $i | cut -f 1)
         echo "<H3>$i</H3>"
         echo "<PRE>"
         printf "$format" "Dirs" "Files" "Size"
         printf "$format" "----" "-----" "----"
         printf "$format" $total_dirs $total_files $total_size
         echo "</PRE>"
     done
     return
 }

Esta reescritura aplica mucho de lo que hemos aprendido hasta ahora. Aún probamos el superusuario, pero en lugar de realizar la lista completa de acciones como parte del if, configuramos algunas variables usadas posteriormente en un bucle for. Hemos añadido varias variables locales a la función y hecho uso deprintf para formatear parte de la salida.

Para saber más

No hay comentarios:

Publicar un comentario

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