if
goto
de C. La
shell no dispone de una orden equivalente directa.
if orden then [[bloque de operaciones si orden se ejecuta con éxito]] else [[bloque de operaciones si orden fracasa]] fiPara saber si una orden se ha ejecutado correctamente, en realidad lo que hace
if
es mirar su código de retorno.
Si es cero, supone que se ha ejecutado bien y si es distinto de
cero, lo contrario. Este es el convenio habitual. Veamos un ejemplo:
<gyermo@ENCINA>/usuarios/profes/gyermo$ if ls -l ModestiaAparte-COmoTeMueves.mp3 > then > echo El fichero existe > else > echo Desafortunadamene, el fichero no existe > fi ModestiaAparte-COmoTeMueves.mp3: No such file or directory Desafortunadamene, el fichero no existeEl primer mensaje que aparece, lo imprime el propio
ls
.
De hecho, está en inglés. La orden ls
devuelve 2 a la shell. La orden if
supone que
se ha ejecutado con error y va al else
, donde
se imprime el mensaje.
elif
, por lo que la
estructura más completa es:
if orden then [[bloque de operaciones si orden se ejecuta con éxito]] elif orden2 then [[bloque de operaciones si orden2 se ejecuta con éxito]] elif orden3 then [[bloque de operaciones si orden3 se ejecuta con éxito]] [[...]] else [[bloque de operaciones si todas las órdenes fracasan]] fiTanto los
elif
s como el else
son
opcionales.
if
anterior parece
insuficiente. Lo suyo es que se nos permita hacer comparaciones
con variables, como en cualquier otro lenguaje de programación.
De hecho, esto es posible, pero a través de órdenes
nuevas.
true
y false
. Ninguna de las dos
hace nada, pero devuelven un valor equivalente a verdadero
(0) y falso (distinto de 0):
<gyermo@ENCINA>/usuarios/profes/gyermo$ true <gyermo@ENCINA>/usuarios/profes/gyermo$ echo $? 0 <gyermo@ENCINA>/usuarios/profes/gyermo$ false <gyermo@ENCINA>/usuarios/profes/gyermo$ echo $? 1La más útil es, sin duda, la orden
test
.
Acepta como parámetros una expresión y devuelve
verdadero (0) o falso (distinto de 0), dependiendo de la
evaluación de dicha expresión.
test
) son:
-a
ruta: verdadero, si la ruta existe-d
ruta: verdadero, si la ruta existe
y es un directorio-f
ruta: verdadero, si la ruta existe
y es un fichero ordinario-r
ruta: verdadero, si la ruta existe
y tenemos permiso de lectura-w
ruta: verdadero, si la ruta existe
y tenemos permiso de escritura-x
ruta: verdadero, si la ruta existe
y tenemos permiso de ejecución/acceso-ot
ruta2: verdadero, si
la ruta1 es anterior a la ruta2-nt
ruta2: verdadero, si
la ruta1 es posterior a la ruta2<gyermo@ENCINA>/usuarios/profes/gyermo$ ls -ld PRUEBA d---r-xrwx 2 gyermo profes 512 ago 18 02:24 PRUEBA <gyermo@ENCINA>/usuarios/profes/gyermo$ test -r PRUEBA <gyermo@ENCINA>/usuarios/profes/gyermo$ echo $? 1 <gyermo@ENCINA>/usuarios/profes/gyermo$ ls -l CARTAS/* -rw------- 1 gyermo profes 186 ago 21 13:31 CARTAS/ReyesMagos.txt -rw------- 1 gyermo profes 186 ago 19 18:57 CARTAS/SantaClaus.txt <gyermo@ENCINA>/usuarios/profes/gyermo$ test CARTAS/ReyesMagos.txt -nt CARTAS/SantaClaus.txt <gyermo@ENCINA>/usuarios/profes/gyermo$ echo $? 0En el primer ejemplo, comprobamos que no tenemos acceso de lectura la subdirectorio
PRUEBA
, mientras que en
el segundo, test
nos dice que la carta a los
Reyes Magos es es posterior a la carta a Santa Claus.
-z
cadena: la cadena tiene longitud nula-n
cadena: la cadena no tiene longitud
nula=
cadena2: las cadenas
son iguales!=
cadena2: las cadenas
son diferentesif
:
<gyermo@ENCINA>/usuarios/profes/gyermo$ if test "$HOSTNAME" = encina > then > echo Estoy en encina > fi Estoy en encinaEs buena idea poner el nombre de la variable entre comillas, pues en el caso de que la variable no exista, de no haber comillas, la shell no le pasa nada en esa posición a
test
y da error. Por ejemplo:
<gyermo@ENCINA>/usuarios/profes/gyermo$ test $PRESI = "Juan Cuesta" bash: test: =: unary operator expectedEl tercer grupo lo forman las comparaciones con números. Las opciones no nos deparan ninguna sorpresa:
-eq
número2: los dos números son iguales-ne
número2: los dos números son diferentes-gt
número2: el primer número es mayor que
el segundo-ge
número2: el primer número es mayor o
igual que el segundo-le
número2: el primer número es menor o
igual que el segundo-lt
número2: el primer número es menor que
el segundo<gyermo@ENCINA>/usuarios/profes/gyermo$ test 007 = 7 <gyermo@ENCINA>/usuarios/profes/gyermo$ echo $? 1 <gyermo@ENCINA>/usuarios/profes/gyermo$ test 007 -eq 7 <gyermo@ENCINA>/usuarios/profes/gyermo$ echo $? 0En el primer caso, como se han comparado cadenas de caracteres, el resultado es falso. Mientras que en el segundo, 007 representa el mismo número que 7 y, por consiguiente, el resultado es verdadero.
test $((007)) -eq 7? ¿Y el de ejecutar:
test $((009)) -eq 7?
!
expresión: verdadera, si la
expresión es falsa-a
expresión2: verdadera, si ambas expresiones
son verdaderas (AND)-o
expresión2: falsa, si ambas expresiones
son falsas (OR)<gyermo@ENCINA>/usuarios/profes/gyermo$ NUMERO=8 <gyermo@ENCINA>/usuarios/profes/gyermo$ if test $(($NUMERO % 2)) -eq 0 -a $NUMERO -gt 2 > then > echo $NUMERO no es primo > else > echo $NUMERO habría que mirar si es primo > fi 8 no es primoExiste precedencia entre los operadores lógicos, pero lo más práctico es usar paréntesis en el caso de que se combinen varias expresiones. Generalicemos el caso anterior para reflejar la propiedad de que todo número par o múltiplo de tres, mayor que tres, no es primo:
<gyermo@ENCINA>/usuarios/profes/gyermo$ NUMERO=9 <gyermo@ENCINA>/usuarios/profes/gyermo$ if test \( $(($NUMERO % 2)) -eq 0 -o \ > $(($NUMERO % 3)) -eq 0 \) \ > -a $NUMERO -gt 3 > then > echo $NUMERO no es primo > else > echo $NUMERO habría que mirar si es primo > fi 9 no es primoComo podéis observar en el ejemplo, hay que anteponer el carácter
\
a los paréntesis para
privarlos del significado especial que les otorga la shell.
También debéis reparar en que se le ha dado formato
a la expresión para que resulte más legible.
Para ello se ha necesitado continuar una orden en la línea
siguiente un par de veces. Esto se logra, de nuevo, tecleando
el carácter \
como último carácter
de la línea, justo antes de pulsar Intro. Se han
escrito en rojo estos caracteres de continuación para que los
localicéis mejor.
while
y until
while
de C. Al igual que esta orden, las
órdenes de la shell ejecutan una y otra vez las
instrucciones de un bloque, dependiendo del resultado de
una condición. También como ocurre en la
orden de C, puede ser que el cuerpo del bucle no se
ejecute ni siquiera una vez.
until
, sin embargo,
continúa efectuando el bucle mientras la condición
sea falsa. Veamos cómo haríamos una cuenta
atrás de 10 a 0, primero con una de ellas y, luego, con
la otra:
<gyermo@ENCINA>/usuarios/profes/gyermo$ CUENTA=10 <gyermo@ENCINA>/usuarios/profes/gyermo$ echo $CUENTA ; while test $CUENTA -gt 0 > do > sleep 1 > CUENTA=$(($CUENTA-1)) > echo $CUENTA > done 10 9 8 7 6 5 4 3 2 1 0 <gyermo@ENCINA>/usuarios/profes/gyermo$ CUENTA=10 <gyermo@ENCINA>/usuarios/profes/gyermo$ echo $CUENTA ; until test $CUENTA -eq 0 > do > sleep 1 > CUENTA=$(($CUENTA-1)) > echo $CUENTA > done 10 9 8 7 6 5 4 3 2 1 0
¿Qué tabla de multiplicar desea? 8 TABLA DE MULTIPLICAR DEL 8 8x0 = 0 8x1 = 8 8x2 = 16 8x3 = 24 8x4 = 32 8x5 = 40 8x6 = 48 8x7 = 56 8x8 = 64 8x9 = 72 8x10 = 80
<gyermo@ENCINA>/usuarios/profes/gyermo$ suma 1 2 3 4 5 1+2+3+4+5=15
case
switch
del C, pero con capacidad
para usar expresiones regulares, como se ve en este rudimentario
ejemplo de guion que trata de determinar el tipo de un fichero:
#!/bin/bash case $1 in *.gif | *.jpg | *.png ) echo El fichero \"$1\" es un fichero gráfico ;; *.txt ) echo El fichero \"$1\" es un fichero de texto ;; * ) echo No sé de qué tipo es el fichero \"$1\" ;; esacY esta es una muestra de su ejecución:
<gyermo@ENCINA>/usuarios/profes/gyermo$ tipo.sh Quijote.txt El fichero "Quijote.txt" es un fichero de texto <gyermo@ENCINA>/usuarios/profes/gyermo$ tipo.sh ElGrito.gif El fichero "ElGrito.gif" es un fichero gráfico <gyermo@ENCINA>/usuarios/profes/gyermo$ tipo.sh MammaMIa.mp3 No sé de qué tipo es el fichero "MammaMIa.mp3"
for
for
de la shell no se parece mucho
a su homónima de C. Esta última no es más
que un bucle while
escrito de otra forma.
En cambio a la orden de la shell le hemos de proporcionar
nosotros una lista y ella iterará sobre los elementos
de esa lista, como en el siguiente ejemplo:
<gyermo@ENCINA>/usuarios/profes/gyermo$ for FRUTAS in limones plátanos melones > do > echo Me gustan mucho los $FRUTAS > done Me gustan mucho los limones Me gustan mucho los plátanos Me gustan mucho los melonesA veces es la shell la que proporciona la lista expandiendo una expresión regular, a veces la lista es el resultado de usar una orden entre acentos graves (
`
) o,
como en el ejemplo, la proporcionamos nosotros directamente.
read
para
que el usuario confirme si desea cambiar los permisos de
cada fichero que se le ha pasado. Podéis hacerlo con
shift
o con la orden for
recién
vista.
return
para acabar. Veamos un primer
ejemplo de función:
<gyermo@ENCINA>/usuarios/profes/gyermo$ function nargumentos > { > return $# > } <gyermo@ENCINA>/usuarios/profes/gyermo$ nargumentos uno dos y tres <gyermo@ENCINA>/usuarios/profes/gyermo$ echo $? 4Y ahora un segundo ejemplo de una función sencilla que calcula el factorial de un número:
<gyermo@ENCINA>/usuarios/profes/gyermo$ function factorial > { > RESULTADO=1 > N="$1" > while test $N -gt 1 > do > RESULTADO=$(($RESULTADO*$N)) > N=$(($N-1)) > done > echo $RESULTADO > true > } <gyermo@ENCINA>/usuarios/profes/gyermo$ factorial 5 120Las funciones definidas de la shell, al igual que las variables, aparecen al dar la orden
set
:
<gyermo@ENCINA>/usuarios/profes/gyermo$ set [[...]] TZ=Europe/Madrid UID=108 USER=gyermo _=4 factorial () { RESULTADO=1; N="$1"; while test $N -gt 1; do RESULTADO=$(($RESULTADO*$N)); N=$(($N-1)); done; echo $RESULTADO; true } nargumentos () { return $# }
<gyermo@ENCINA>/usuarios/profes/gyermo$ function genera20 > { > i=0 > while test $i -lt 20 > do > i=$(($i+1)) > echo $i > done > } <gyermo@ENCINA>/usuarios/profes/gyermo$ genera20 | grep 1 1 10 11 12 13 14 15 16 17 18 19Y no solo eso, las propias estructuras de control de la shell, también admiten redirigirse, como se ve en este ejemplo que escribe un asterisco por cada línea que se recibe:
<gyermo@ENCINA>/usuarios/profes/gyermo$ cat > prueba.txt Uno Dos Tres Cuatro <gyermo@ENCINA>/usuarios/profes/gyermo$ while read > do > echo "*" > done < prueba.txt * * * *Si la redirección se hace en la definición de una función, esta se realiza cuando la función se ejecuta:
<gyermo@ENCINA>/usuarios/profes/gyermo$ function zutana > { > echo -n Comienza la ejecuciOn: > date > sleep 7 > echo -n Fin de la ejecuciOn: > date > } > log.txt <gyermo@ENCINA>/usuarios/profes/gyermo$ zutana [[Siete segundos depués...]] <gyermo@ENCINA>/usuarios/profes/gyermo$ cat log.txt Comienza la ejecuciOn:lunes 27 de octubre de 2014 14H05'14" CET Fin de la ejecuciOn:lunes 27 de octubre de 2014 14H05'22" CET
<gyermo@ENCINA>/usuarios/profes/gyermo$ function unoAlDiez > { > echo "Descriptor de la primera salida: $1" > echo "Descriptor de la segunda salida: $2" > > for I in 1 2 3 4 5 6 7 8 9 10 > do > if test $(($I%2)) -eq 0 > then > echo $I >>$1 # Los pares por la primera salida > else > echo $I >>$2 # Los impares por la segunda salida > fi > done > }Es una función que, con el permiso de sus familiares más queridos, es de lo más vulgar. De hecho, si la invocamos con el nombre de dos ficheros, en el primero almacena los pares y en el segundo los impares:
<gyermo@ENCINA>/usuarios/profes/gyermo$ unoAlDiez pares.txt impares.txt Descriptor de la primera salida: pares.txt Descriptor de la segunda salida: impares.txt <gyermo@ENCINA>/usuarios/profes/gyermo$ cat pares.txt 2 4 6 8 10 <gyermo@ENCINA>/usuarios/profes/gyermo$ sort -r impares.txt 9 7 5 3 1Solo nos queda, para conseguir nuestro objetivo, poder prescindir de los ficheros intermedios. La notación es la siguiente:
<gyermo@ENCINA>/usuarios/profes/gyermo$ unoAlDiez >(cat) >(sort -r) Descriptor de la primera salida: /dev/fd/63 Descriptor de la segunda salida: /dev/fd/62 9 7 5 3 1 2 4 6 8 10Lo que ocurre es que la shell arranca la orden
cat
y crea una tubería que la une con unoAlDiez
,
justo como si de una tubería normal se tratase. La
salida de la tubería conecta con la entrada del
cat
, de modo normal. Pero la entrada de la
tubería se sustituye y se pasa como parámetro
a unoAlDiez
, para que pueda escribir lo que
quiera fácilmente. Lo mismo se hace con el segundo
proceso (sort
).
<gyermo@ENCINA>/usuarios/profes/gyermo$ I=1;while test $I -lt 15; do echo $I;I=$(($I+2));done 1 3 5 7 9 11 13Ahora, la sucesión de Fibonacci:
<gyermo@ENCINA>/usuarios/profes/gyermo$ I=0;J=1;while test $(($I+$J)) -lt 15; do J=$(($I+$J));I=$(($J-$I));echo $J;done 1 2 3 5 8 13Finalmente, ¡Tachán!, la solución (se ha partido la línea de la órden en tres partes con ayuda de la barra invertida, por claridad):
<gyermo@ENCINA>/usuarios/profes/gyermo$ diff \ > <(I=1;while test $I -lt 15; do echo $I;I=$(($I+2));done) \ > <(I=0;J=1;while test $(($I+$J)) -lt 15; do J=$(($I+$J));I=$(($J-$I));echo $J;done) 1a2 > 2 4,6c5 < 7 < 9 < 11 --- > 8Traduciendo el resultado a la lengua del pueblo llano, se nos viene a decir que para conseguir la lista de los números de la sucesión de Fibonacci menores que 15, tomemos los números impares menores que 15, añadamos el 2 y sustituyamos el 7,9 y 11 por el 8.
if then elif else fi
true
false
test
while do done
until do done
case in esac
for in do done
function { }
return