Conversación de bar IV
- Y entonces amigo mio, intentaste hacer el ejercicio que te pedí para reforzar tus conocimientos?
- Claro, que si! En programación, si no se practica, no se aprende. Me pediste que hiciera un
scriptisiño para informar si un determinado usuario, que será pasado como parámetro, esta logado (ajjjj!) o no. Hice lo siguiente:
$ cat logado
#!/bin/bash
# Busca si una persona está logada o no
if who | grep $1
then
echo $1 está logado
else
echo $1 no se encuentra en la vecindad
fi
- Calma amigo! Ya vi que hoy llegaste lleno de deseos de trabajar, primero vamos a pedir nuestros "choppes" de costumbre y después vamos al
Shell. Chico!, tráeme dos "choppes", uno sin espuma!
- Ahora que ya mojamos nuestros labios, vamos a echar un vistazo a la ejecución de tu programa:
$ logado jneves
jneves pts/0 Oct 18 12:02 (10.2.4.144)
jneves está logado
Realmente funcionó. Pasé mi
login como parámetro y él me informó que estaba logado, sin embargo, al mismo tiempo salió una línea que no pedí. Esta línea es la salida del comando
who, y para evitar que eso pase, lo único que hay que hacer es mandarla hacia el agujero negro que a estas altura ya sabes que es el
/dev/null. Veamos entonces como quedaría:
$ cat logado
#!/bin/bash
# Busca si una persona está logada o no (versión 2)
if who | grep $1 > /dev/null
then
echo $1 está logado
else
echo $1 no se encuentra en la vecindad
fi
Ahora vamos a los tests:
$ logado jneves
jneves está logado
$ logado chico
chico no se encuentra en la vecindad

Ah, ahora si! Acuérdate de esto: la mayor parte de los comandos tienen una salida patrón y una salida de errores (el
grep es una de las pocas excepciones, ya que no da mensajes de error cuando no encuentra una cadena) y es necesario estar muy atentos para redirecionarlas hacia el agujero negro cuando sea necesario.
Bueno, ahora vamos a cambiar de asunto: la última vez que nos encontramos aquí en el Bar, te estaba mostrando los comandos condicionales y cuando ya estábamos con la garganta seca hablando sobre el if, me preguntaste como se verifican condiciones. Veamos entonces
El Comando test
Bien, todos estamos acostumbrados a usar el
if para verificar condiciones, y estas condiciones siempre son: mayor, menor, mayor o igual, menor o igual, igual y diferente. En
Shell para verificar condiciones, usamos el comando
test, sólo que este es mucho más poderoso de lo que estamos habituados. Primero te voy a mostrar las principales opciones (existen muchas otras), para verificar la existencia de archivos en el disco:
| Opciones del Comando test para archivos |
-x arch |
arch existe y con derechos de ejecución |
| Opción |
Verdadero si: |
-e arch |
arch existe |
-s arch |
arch existe y tiene tamaño mayor que cero |
-f arch |
arch existe y es un archivo regular |
-d arch |
arch existe y es un directorio; |
-r arch |
arch existe y con derechos de lectura |
-w arch |
arch existe y con derechos de escritura |
Observa ahora las principales opciones para verificar cadenas de caracteres:
Opciones del comando test para cadenas de caracteres |
c1 = c2 |
Cadena c1 y c2 son idénticas |
| Opción |
Verdadero si: |
-z cadena |
Tamaño de cadena es cero |
-n cadena |
Tamaño de cadena es mayor que cero |
cadena |
La cadena cadena tiene tamaño mayor que cero |
Y crees que se acabó ahí? Pues estás engañado! Ahora viene la parte a la que estás más acostumbrado, o sea las famosas comparaciones con números. Fijate en la tabla que sigue:
Opciones del comando test para números |
n1 -le n2 |
n1 es menor o igual a n2 |
less or equal |
| Opción |
Verdadero si: |
Significado |
n1 -eq n2 |
n1 y n2 son iguales |
equal |
n1 -ne n2 |
n1 y n2 no son iguales |
not equal |
n1 -gt n2 |
n1 es mayor que n2 |
greater than |
n1 -ge n2 |
n1 es mayor o igual a n2 |
greater or equal |
n1 -lt n2 |
n1 es menor que n2 |
less than |
Además de todo eso, se suman a las opciones que te mostré las siguientes opciones:
| Operadores |
-o |
O lógico |
| Operador |
Finalidad |
Paréntesis ( ) |
Agrupar |
Admiración ! |
Negar |
-a |
Y lógico |
Ufa! Como viste hay mucha cosa y como te dije al comienzo, nuestro
if es mucho más poderoso que los de otros. Vamos a ver en unos ejemplos como funciona todo esto, primero verificaremos la existencia de un directorio:
Ejemplos:
if test -d lmb
then
cd lmb
else
mkdir lmb
cd lmb
fi
En este ejemplo, verifiqué la existencia de un directorio definido
lmb, en caso negativo (
else), éste seria creado. Ya sé, vas a criticar mi razonamiento diciendo que el
script no está optimizado. Lo sé perfectamente, pero quería que entendieras este ejemplo, para poder usar después el signo de admiración (
!) como un negador del
test. Mira esto:
if test ! -d lmb
then
mkdir lmb
fi
cd lmb
De esta forma el directorio
lmb sería creado solamente si este no existiese, y esta negación se debe al signo de admiración (
!) que precede a la opción
-d. Al finalizar la ejecución de este fragmento de
script, el programa estaría seguramente dentro del directorio
lmb.
Vamos a ver dos ejemplos para entender como se diferencia la comparación entre números y entre cadenas.
cad1=1
cad2=01
if test $cad1 = $cad2
then
echo Las variables son iguales.
else
echo Las variables son diferentes.
fi
Ejecutando el fragmento del programa arriba, resulta:
Las variables son diferentes.
Vamos a modificarlo un poco, de manera que la comparación esta vez sea numérica:
cad1=1
cad2=01
if test $cad1 -eq $cad2
then
echo Las variables son iguales.
else
echo Las variables son diferentes.
fi
Y lo ejecutamos nuevamente:
Las variables son iguales.
Como viste, en las dos ejecuciones obtuve resultados diferentes porque la cadena
01 es realmente diferente de la cadena
1, sin embargo, la cosa cambia cuando las variables son verificadas en forma numérica, ya que el número
1 es igual al número
01.
Ejemplos:
Para mostrar el uso de los conectores
-o (
O) y
-a (
Y), tengo un ejemplo bien grosero, hecho directamente en el
prompt (pido disculpas a los zoólogos, ya que no entendiendo nada de reino, clase, orden, familia, género y especie, puede que lo que estoy llamando familia o género tenga grandes posibilidades de ser incorrecto):
$ Familia=felina
$ Genero=gato
$ if test $Familia = canina -a $Genero = lobo -o $Familia = felina -a $Genero = leon
> then
> echo Cuidado
> else
> echo Se puede acariciar
> fi
Se puede acariciar
En este ejemplo en caso de que el animal fuera de la familia canina
Y (
-a) del género lobo,
O (
-o) de la familia felina
Y (
-a) del género leon, se daria un aviso de alerta, en caso contrario el mensaje sería de incentivo.

Los signos de mayor (
>) al inicio de las líneas internas al
if son los
prompts de continuación (que están definidos en la variable
$PS2) y cuando el
Shell identifica que un comando continuará en la línea siguiente, automáticamente los va colocado, hasta que el comando sea finalizado.
Vamos a cambiar el ejemplo para ver si continúa funcionando:
$ Familia=felino
$ Genero=gato
$ if test $Familia = felino -o $Familia = canino -a $Genero = onza -o $Genero = lobo
> then
> echo Cuidado!
> else
> echo Puede acariciar
> fi
Cuidado!
Obviamente la operación resultó en error, ya que la opción
-a tiene prioridad sobre la
-o, y así lo que se evaluó primero, fué la expresión:
$Familia = canino -a $Genero = onza
Que fué evaluada como falsa, y dió el seguiente resultado:
$Familia = felino -o FALSO -o $Genero = lobo
Que una vez resuelta dió:
VERDADERO -o FALSO -o FALSO
Como ahora todos los conectores son
-o, y para que una serie de expresiones conectadas entre sí por diversos
O lógicos sea verdadera, basta que una de ellas lo sea, la expresión final resultó como
VERDADERO y el
then fue ejecutado de forma incorrecta. Para que vuelva a funcionar hagamos lo seguiente:
$ if test \($Familia = felino -o $Familia = canino\) -a \($Genero = onza -o $Genero = lobo\)
> then
> echo Cuidado!
> else
> echo Puede acariciar
> fi
Puede acariciar
De esta forma, con el uso de los paréntesis agrupamos las expresiones con el conector
-o, dando prioridad a sus ejecuciones y resultando:
VERDADERO -a FALSO
Para que sea
VERDADERO el resultado de dos expresiones ligadas por el conector
-a es necesario que ambas sean verdaderas, lo que no es el caso del ejemplo arriba citado. Así el resultado final fue
FALSO, siendo entonces el
else correctamente ejecutado.
Si quisieramos escojer un CD que tenga músicas de 2 artistas diferentes, nos sentimos tentados a usar un
if con el conector
-a, pero siempre es bueno recordar que el
bash nos dá muchos recursos y eso podría ser hecho de forma mucho más simple con un único comando
grep, de la siguiente manera:
$ grep Artista1 musicas | grep Artista2
De la misma forma, para escojer CDs que tengan la participación del
Artista1 y del
Artista2, no es necesario montar un
if con el conector
-o. El
egrep (o
grep -E, siendo éste más recomendable), también nos resuelve eso. Fijate como:
$ egrep (Artista1|Artista2) musicas
O (en ese caso específico) el propio
grep puro y simple podría ayudarnos:
$ grep Artista[12] musicas
En el
egrep arriba, fue usada una expresión regular, donde la barra vertical (
|) trabaja como un
O lógico y los paréntesis son usados para limitar la amplitud de éste
O.Ya en el
grep de la línea siguiente, la palabra
Artista debe ser seguida por alguno de los valores de la lista formada por los paréntesis rectos (
[ ]), o sea,
1 o
2.
- Está bien, acepto el argumento, el
if del
Shell es mucho más poderoso que los otros conocidos, pero entre nosotros, esa construción del
if test ... es muy extraña, y poco legible.
- Si, tienes toda la razón, tampoco me es simpática y me parece que a nadie le gusta. Creo que fue por eso, que el
Shell incorporó otra sintáxis que substituye el comando
test.
Ejemplos:
Para esto vamos a ver nuevamente aquél ejemplo para cambiar de directorios, que era así:
if test ! -d lmb
then
mkdir lmb
fi
cd lmb
y utilizando la nueva sintáxis, vamos a hacerla así:
if [ ! -d lmb ]
then
mkdir lmb
fi
cd lmb
O sea, el comando
test puede ser substituído por un par de paréntesis rectos (
[ ]), separados por espacios en blanco de los argumentos, lo que aumentará enormemente la legibilidad, pues el comando
if quedara con una sintáxis parecida a de otras lenguajes y por este motivo, usaré el comando
test de esta forma de ahora en adelante.
Querida, Encojieron el Comando Condicional!
Si creees que se acabó, estás muy equivocado. Repara en la tabla (booleana) siguiente:
| Valores Booleanos |
Y |
O |
| FALSO-FALSO |
FALSO |
FALSO |
| VERDADERO-VERDADERO |
VERDADERO |
VERDADERO |
| VERDADERO-FALSO |
FALSO |
VERDADERO |
| FALSO-VERDADERO |
FALSO |
VERDADERO |
O sea, cuando el conector es
Y y la primera condición es verdadera, el resultado final puede ser
VERDADERO o
FALSO, dependiendo de la segunda condición, ya en el conector
O, en caso que la primera condición sea verdadera, el resultado siempre será
VERDADERO y si la primera fuera falsa, el resultado dependerá de la segunda condición.
Bueno, las personas que crearon el interpretador no son bobas y están siempre intentando optimizar al máximo los algoritmos. Por tanto, en el caso del conector
Y, la segunda condición no será evaluada, en el caso de que la primera sea falsa, ya que el resultado será siempre
FALSO. Ya con el
O, la segunda será ejecutada solamente en el caso de que la primera sea falsa.
Aprovechando eso, crearon una forma abreviada de hacer tests. Bautizaron el conector
Y de
&& y el
O de
|| y para ver como funciona esto, vamos a usarlos como test en nuestro viejo ejemplo de cambiar de directorio, que en su última versión estaba así:
if [ ! -d lmb ]
then
mkdir lmb
fi
cd lmb
Eso también podría ser escrito de la siguiente manera:
[ ! -d lmb ] && mkdir lmb
cd lmb
O inclusive sacando la negación (
!):
[ -d lmb ] || mkdir lmb
cd lmb
En el primer caso, si el primer comando (el
test que está representado por los paréntesis rectos) estuviera bien resultado, o sea, no existe el directorio
lmb, el
mkdir será ejecutado, porque la primera condición era verdadera y el conector era
Y.
En el ejemplo siguiente, verificaremos si el directorio
lmb existe (en el anterior verificabamos si no existía) y en caso de que eso fuera verdadero, el
mkdir no sería ejecutado porque el conector era
O.
Otra forma:
cd lmb || mkdir lmb
En este caso, si el
cd diera error, sería creado el directorio
lmb pero no sería ejecutado el
cd hacia dentro de él. Para ejecutar más de un comando de esta forma, es necesario que hagamos un agrupamiento de comandos, eso se consigue con el uso de llaves (
{ }). Mira como sería la forma correcta:
cd lmb ||
{
mkdir lmb
cd lmb
}
Todavia no está bien, porque en el caso de que el directorio no exista, el
cd dará el mensaje de error correspondiente. Entonces debemos hacer:
cd lmb 2> /dev/null ||
{
mkdir lmb
cd lmb
}
Como viste, el comando
if nos permitió hacer un
cd seguro de diversas maneras. Es siempre bueno recordar que el seguro a que me referí, es en lo referente al hecho de que al final de la ejecución, tu siempre estarás dentro de
lmb, siempre que tengas permisos para entrar en
lmb, permisos para crear un directorio en
../lmb, haya espacio en el disco, ...
Y toma test!
Piensas que ya se acabó? Gran error! Todavia tenemos una forma de
test más. Esta es muy buena porque te permite usar patrones para la comparación. Estos patrones atienden a las normas de Generación de Nombres de Archivos (
File Name Generation, que son ligeramente parecidas con las Expresiones Regulares, pero no pueden ser confundidas con éstas).
La diferencia de sintáxis de este para el
test que acabamos de ver, es que este trabaja con dos parêntesis rectos de la siguiente forma:
[[ expresión ]]
Donde
expresión es una de las que constan en la tabla siguiente:
| Expresiones Condicionales Para Padrones |
expr1 ¦¦ expr2 |
"O" lógico, Verdadero si expr1 o expr2 fueran Verdaderos |
| Expresión |
Retorna |
cadena == padrón cadena1 = padrón |
Verdadero si cadena1 es igual a padrón |
cadena1 != padrón |
Verdadero si cadena1 no es igual a padrón. |
cadena1 < cadena1 |
Verdadero si cadena1 está antes de cadena1 alfabéticamente. |
cadena1 > cadena1 |
Verdadero si cadena1 está después de cadena1 alfabéticamente |
expr1 && expr2 |
"Y" lógico, Verdadero si ambos expr1 y expr2 son Verdaderos |
$ echo $H
13
$ [[ $H == [0-9] || $H == 1[0-2] ]] || echo Hora no válida
Hora no válida
$H=12
$ [[ $H == [0-9] || $H == 1[0-2] ]] || echo Hora no válida
$
En este ejemplo,verificamos si el contenido de la variable
$H esta comprendido entre cero y nueve
([0-9]) o
(||) si esta entre diez y doze
(1[0-2]), dando un mensaje de error en caso que no sea asi.
Ejemplos:
Para saber si una variable tiene el tamaño de un y solamente un caracter, haz:
$ var=a
$ [[ $var == ? ]] && echo var tiene un caracter
var tiene un caracter
$ var=aa
$ [[ $var == ? ]] && echo var tiene un caracter
$
Como puedes imaginar, este uso de patrones para comparación, aumenta mucho el poderío del comando
test.
En el inicio de esta conversación, antes del último "choppe", afirmabamos que el comando
if del interpretador
Shell es más poderoso que sus similares en otros lenguajes. Ahora que conocimos todo su espectro de funciones, dime: estas de acuerdo o no con esta afirmación?
Acaso Casa con case
Veamos un ejemplo didáctico: dependiendo del valor de la variable
$opc el
script deberá ejecutar una de las opciones: inclusión, exclusión, alteración o fin. Fijate como quedaría este fragmento de
script:
if [ $opc -eq 1 ]
then
inclusión
elif [ $opc -eq 2 ]
then
exclusión
elif [ $opc -eq 3 ]
then
alteración
elif [ $opc -eq 4 ]
then
exit
else
echo Digite una opción entre 1 y 4
fi
En este ejemplo viste el uso del
elif con un
else if, esta es una sintáxis válida y aceptada, pero podríamos hacerlo mejor y sería usando el comando
case, que tiene la sintáxis siguiente:
case $var in
patrón1) cmd1
cmd2
cmdn ;;
patrón2) cmd1
cmd2
cmdn ;;
patrónn) cmd1
cmd2
cmdn ;;
esac
Donde la variable
$var es comparada a los patrones
patrón1, ..., patrónn y en el caso de que uno de ellos coincida, el bloque de comandos
cmd1, ..., cmdn correspondiente será ejecutado hasta que encuentre un doble punto y coma (
;;), en donde el flujo del programa se desviará hacia la instrucción inmediatamente siguiente, o sea el
esac.
En la formación de los patrones, son aceptados los siguientes caracteres:
| Caracteres Para Formacion de Padrones |
¦ |
O lógico |
| Caracter |
Significado |
* |
Cualquier caracter ocurriendo cero o más veces |
? |
Cualquier caracter ocurriendo una vez |
[...] |
Lista de caracteres |
Para mostrar como realmente queda mejor, vamos a repetir el ejemplo anterior, sólo que esta vez usaremos el
case y no el
if ... elif ... else ... fi.
case $opc in
1) inclusión ;;
2) exclusión ;;
3) alteración ;;
4) exit ;;
*) echo Digite una opción entre 1 y 4
esac
Como debes haberte dado cuenta, usé el asterisco como la última opción, o sea, si el asterisco quiere decir cualquier cosa, entonces servirá para cualquier cosa que no este en el intervalo del 1 al 4. Otra cosa a tener en cuenta es que el doble punto y coma no es necesario antes del
esac.
Ejemplos:
Vamos ahora a hacer un
script más radical. Te dará los buenos días, buenas tardes o buenas noches, dependiendo de la hora en que sea ejecutado, pero primero mira estos comandos:
$ date
Tue Nov 9 19:37:30 BRST 2004
$ date +%H
19
El comando
date informa de la fecha completa del sistema, sin embargo tiene diversas opciones para su enmascaramiento. En este comando, la formatación comienza con un signo de más (
+) y los caracteres de formatación vienen después de un signo de porcentaje (
%), así el
%H significa la hora del sistema. Dicho esto, vamos al ejemplo:
$ cat bienvenido.sh
#!/bin/bash
# Programa bien educado que
# da los Buenos dias, buenas tardes o
# las buenas noches dependiendo de la hora
Hora=$(date +%H)
case $Hora in
0? | 1[01]) echo Buenos días
;;
1[2-7] ) echo Buenas tardes
;;
* ) echo Buenas noches
;;
esac
exit
Fue pesado, verdad?. - Que vá!. Vamos a desmenuzar la resolución caso a caso (o sería case-a-case?

)
0? | 1[01] - Significa cero seguido de cualquier cosa (
?), o (
|) uno seguido de cero o uno (
[01]) o sea, esta línea pegó 01, 02, ... 09, 10 y 11;
1[2-7] - Significa uno seguido de la lista de dos a siete, o sea, esta línea pegó 12, 13, ... 17;
* - Significa todo aquello que no se encuadró en ninguno de los patrones anteriores.
- Mira, hasta ahora hablé mucho y bebí poco. Ahora te voy a pasar un ejercicio para que lo hagas en tu casa y me des la respuesta la próxima vez que nos encontremos aqui en el bar, de acuerdo?
- De acuerdo, pero antes informe a las personas que nos están acompañando en este curso, como pueden hacerlo para encontrarle, para hacerle críticas, hacer chistes, invitarle a una cerveza, un curso, unas charlas o hasta si quieren, para hablar mal de los políticos.
- es fácil, mi e-mail es
julio.neves@gmail.com, pero para de distraerme, que no me voy a olvidar de pasarte el
script de los deberes, y este es: quiero que hagas un programa que recibirá como parámetro el nombre de un archivo y que cuando sea ejecutado grabe este archivo con el nombre original seguido de una tilde (
~), además colocaras este archivo dentro del
vi (de paso, el mejor editor del cual se tiene noticia) para ser editado. Esto sirve para tener siempre la última copia buena del archivo en el caso de que la persona haga alteraciones indebidas. Obviamente, tendrás que hacer las investigaciones necesarias, como verificar si fué pasado un parámetro, si el archivo que fué pasado existe, ... En fin, lo que se te venga a la cabeza y tu creas que deba estar en el
script. Entendiste?
- Hum, hum...
- Chico! Traeme otra más sin espuma, que aquí el amigo ya está pensando!
Gracias y hasta la
próxima
--
HumbertoPina - 05 Oct 2006