Página siguiente Página anterior Índice general

6. Control del flujo del programa

Ahora es cuando vamos a empezar a ser capaces de programar algo en shell. Las estructuras de control de flujo del programa que tenemos disponibles cuando programamos en shell son el if, case, while, for y until. Además, veremos algunas órdenes especiales y algunas construcciones un poco más raras, que sirven también para controlar el curso de los acontecimientos.

6.1 Estructura if

La estructura if tiene una sintaxis algo inusual, porque necesita la palabra then, pero en la línea siguiente a donde está el if y la condición. Es un if bastante versátil, ya que permite cláusulas elif (else if) y else. La palabra para indicar el fin de la estructura if es la palabra fi (if al revés).

La cláusula elif, por si no lo sabes, es parecida al else, aunque necesita una condición después. Para comprender mejor el funcionamiento, veamos la siguiente equivalencia:

        if [ condicion ]                        if [ condicion ]
        then                                    then
                ...                                     ...
        else                                    elif [ condicion2 ]
                if [ condicion2 ]               then
                then                                    ...
                        ...                     fi
                fi
        fi

Algunas personas, para hacer la sintaxis más clara, o al menos más parecida a otros lenguajes, como Pascal, escriben el if de la siguiente forma:

        if [ condicion ]; then
        fi

Para decidir qué ejecutar, la estructura if permite, además de una condición, el nombre de un programa. Lo que se hará entonces es ejecutar el programa con los parámetros dados, y dar como verdadero (y ejecutar lo que haya entre el if y el fi o el elif o el else) que el programa devuelva un cero.

En estos casos es muy útil la instrucción nula (:), ya que si lo que queremos es ejecutar un código si el programa va mal, entonces la única forma de hacerlo es la siguiente:

        if orden
        then
                :
                # No hacemos nada
        else
                codigo
                ...
        fi

Como ejemplo tomaremos unas líneas que están en mi fichero ~/.bashrc:

if set | grep DISPLAY &>/dev/null; then
        alias vi="vi -g"
fi

Esto ejecuta la orden set, que da las variables de entorno. Las filtra para quedarnos sólo con la que contenga la variable $DISPLAY (si es que está), y manda toda la salida a un agujero negro (/dev/null). Esto se hace para comprobar el código de salida del grep, que devolverá verdadero si deja ``salir'' alguna línea. Es decir: si está definida la variable $DISPLAY, se entra a ejecutar el alias (en realidad, se entra si existe la variable $DISPLAY o si alguna variable tiene como contenido algo con la ristra DISPLAY, lo cual es bastante improbable. Hay una forma de afinar la condición, te lo dejo como ejercicio). Si el grep devuelve falso, no se ejecuta nada.

6.2 Condiciones particulares

Hay dos condiciones que son relativamente comunes, que son ejecutar un código si algo inmediatamente anterior ha ido bien, o justo lo contrario, ejecutarlo sólo si lo anterior ha ido mal.

Por ello, hay dos ``estructuras'' que nos permiten manejar estas situaciones de una forma más cómoda y limpia. Estas estructuras son && y ||. Para las personas que hayan programado en C, les resultarán familiares. El && es el equivalente a la palabra reservada de Pascal and y || es el equivalente del or en Pascal.

La forma de entender qué produce cada estructura, es pensar que el intérprete sólo va a ejecutar lo estrictamente necesario para saber cómo termina la condición. Sabemos que false && cualquiercosa da falso, y que true || cualquiercosa da verdadero, así que eso es lo que va a hacer el intérprete: si utilizamos la estructura &&, ejecutará la primera instrucción, y sólo si va bien (si devuelve un cero) ejecutará la segunda, y análogamente con la estructura ||, el intérprete ejecutará la primera instrucción, y sólo ejecutará la segunda si la primera ha ido mal, es decir, si el valor de la condición total depende de lo que pase al ejecutar la segunda instrucción.

La forma de acordarse es pensarlo como en lenguaje natural: ``Hazlo y te odiaré para siempre''. O sea, que si la primera instrucción se ejecuta (...satisfactoriamente; esto es, devolviendo un 0), pasará la segunda. Análogamente, ``Hazlo o te odiaré para siempre''. O sea, si la primera no se ejecuta (satisfactoriamente), se ejecutará la segunda. Pero dejémosnos de amenazas ;-).

La estructura && puede utilizarse cuando ejecutemos una instrucción cuyo trabajo depende de que la primera haya ido bien, y la || puede ejecutarse para dar mensajes de error:

        cd foo || echo "¡No puedo entrar en 'foo'!"

6.3 Estructura case

La estructura case del Bourne Shell y compatibles es una estructura parecida a la de Pascal: cuando entra en la estructura, sale cuando empiece la siguiente etiqueta (no así en C, en el que las etiquetas del case actúan como etiquetas de un JMP en ensamblador: una vez que entran en una etiqueta, no salen del case hasta que éste acaba completamente).

Las etiquetas pueden ser cualquier ``expresión regular'' (expresión con comodines típica de los intérpretes de órdenes; no las expresiones regulares del vi, el grep, el perl y otros) válida, con lo que se puede poner un * como última etiqueta del case para actuar como else. Para indicar que termina el código de una etiqueta determinada, hay que poner al final de la última orden dos signos de punto y coma seguidos.

La estructura general del case es:

        case valor
        in
                expreg1)
                        ...
                        ultimaorden1;;
                expreg2)
                        ...
                        ultimaorden2;;
                ...
                expregn)
                        ...
                        ultimaordenn;;
        esac

Por ejemplo, si queremos comprobar una respuesta de sí o no (de forma un poco relajada), podemos hacer:

        case $resp; in
                s*)
                        echo "Has contestado sí (o algo parecido)"
                n*)
                        echo "Has contestado no (o algo parecido)"
                *)
                        echo "Has contestado alguna otra cosa"
        esac

6.4 Estructura while

La estructura básica para construir bucles. La sintaxis es:

        while orden
        do
                ...
        done

En orden podemos poner una orden normal y corriente (el bucle se ejecutaría mientras la orden devolviera un cero), o podríamos también poner una condición, con la orden test (o mejor, con la sintaxis alternativa []), que por otra parte no deja de ser una orden como otra cualquiera.

Si quisiésemos estar seguros de que la contestación a la pregunta del ejemplo anterior era s o n, podríamos haber hecho lo siguiente:

        while [ $resp != "s" -a $resp != "n" ]; do
                read resp
        done

Y justo debajo podríamos poner el case anterior, para comprobar cuál fue finalmente la respuesta.

6.5 Estructura for

Es otra de las estructuras importantes en los lenguajes de programación. Es más versátil que el equivalente de Pascal, pero menos que el equivalente de C (como era lógico, por otra parte).

Es una estructura que permite dos sintaxis: La primera, la del for más o menos tradicional (en los lenguajes interpretados), es decir, dar el nombre de una variable y los valores por los que tiene que ``pasar'', es la siguiente:

        for variable in expreg1 expreg2 ...  expregn
        do
                ...
        done

Hay una pequeña diferencia respecto a los bucles normales for, y ésta es que los valores por los que pasa la variable variable tenemos que especificarlos uno a uno (mediante expresiones regulares). Las expresiones regulares se intentarán hacer coincidir con los nombres de los ficheros del directorio actual.

Por ejemplo, si quisiéramos coger todos los ficheros normales y hacerles una sustitución con el sed, por ejemplo, una opción sería utilizar un bucle for. Por ejemplo, yo utilizo de vez en cuando bucles así para pasar texto de MS-DOS a texto UNIX:

        for fichero in *.txt; do
                sed 's/^M//' $fichero >TEMPORAL
                mv TEMPORAL $fichero
        done

En este bucle, cogemos el sed y sustituimos los caracteres de retorno de carro por nada, o sea, los borramos. Esto convierte los ficheros de texto de MS-DOS a UNIX. Pero sed manda el resultado a la salida estándar, así que tenemos que dirigirlo a otro fichero (el TEMPORAL. Aquí suponemos que no existe ningún fichero llamado TEMPORAL en el directorio actual, o por lo menos que no es importante y lo podemos borrar). Una vez tenemos el resultado en el fichero TEMPORAL, simplemente lo movemos al fichero original, sobreescribiendo el fichero antiguo y poniendo la versión nueva, sin los caracteres de retorno de carro.

La segunda sintaxis que permite el bucle for es una sintaxis sin lista. Si utilizamos esta sintaxis, el intérprete lo entenderá como que la lista que queremos es la de todos los parámetros dados al guión desde la línea de órdenes (u otro guión o programa). La sintaxis queda:

        for variable
        do
                ...
        done

Si tuviéramos un programa que sólo aceptara ficheros u opciones, podríamos discriminar entre unos y otras así:

        for par; do
                case $par; in
                        -*)
                                echo "Opción '$par' (empieza por '-')"
                        *)
                                echo "Fichero '$par' (cualquier otra cosa)"
                esac
        done

6.6 Estructura until

Esta estructura es parecida a la while, aunque la condición la damos ``al revés'', es decir, que el bucle se ejecuta mientras la condición a comprobar sea falsa, y termina cuando sea verdadera. Tiene una utilidad diferente que en los lenguajes de programación. Normalmente se utiliza en los lenguajes de alto nivel para forzar la ejecución del bucle al menos una vez, aunque la condición sea desde el principio verdadera. No se puede utilizar de esa manera en la programación en shell, porque la sintaxis tiene la condición al principio.

Su uso es sólo para mejorar la legibilidad, porque podemos negar incluso la salida de los programas que llamemos, anteponiendo a la llamada al programa el caracter de negación !.

La sintaxis es como sigue:

        until orden
        do
                ...
        done

6.7 Ruptura de la ejecución normal de los bucles

Algunas veces necesitamos romper la ejecución normal de los bucles. Quizás queramos saltarnos lo que queda de iteración, o quizás necesitemos en un momento dado salir del bucle.

Las dos órdenes utilizadas en la programación en shell para hacer estas cosas son las dos mismas que se utilizan en lenguaje C, y se utilizan de la misma manera: break y continue.

La primera sirve para salir del bucle y seguir la ejecución del programa, y la segunda sirve para saltarnos lo que queda de iteración y empezar la siguiente. Antes de empezar con la siguiente iteración, se volverá a chequear la condición (supongo).


Página siguiente Página anterior Índice general