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.
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.
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'!"
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
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.
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
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
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).