Conversación de bar Parte V
- Que hay amigo! Ordenaste ya tus ideas?, Se fundió ya tu cabeza, o todavía aguanta más
Shell?
- Aguanto, claro! Me esta gustando mucho! Me gustó tanto que hasta me esmeré en el ejercicio que me dejaste. Te acuerdas que me pediste que hiciera un programa que recibiría como parámetro el nombre de un archivo y que cuando se ejecutara salvara este archivo con el nombre original seguido de una tilde (
~) ademas de colocarlo dentro del
vi?
- Claro que me acuerdo, muéstramelo y explica como lo hiciste.
$ cat vira
#!/bin/bash
#
# vira - vi grabando el archivo anterior
# == = =
# Verificando si fue pasado 1 parámetro
if [ "$#" -ne 1 ]
then
echo "Erro -> Uso: $0 "
exit 1
fi
Arq=$1
# En caso de que el archivo no exista, no hay copia para grabar
if [ ! -f "$Arq" ]
then
vi $Arq
exit 0
fi
# Si no puedo alterar el archivo, para que voy a usar el vi?
if [ ! -w "$Arq" ]
then
echo "Usted no tiene privilegios de grabación en $Arq"
exit 2
fi
# Ya que está todo OK, voy a salvar la copia y llamar el vi
cp -f $Arq $Arq~
vi $Arq
exit 0
- Bárbaro, muy bien! Pero dime una cosa: porque terminaste el programa con un
exit 0?
- Ahhh! Descubri que el número después del
exit da el código de retorno del programa (o
$?, te acuerdas?), y de esta forma, si todo se ejecuto bien, se cerraría con el
$? = 0. Sin embargo, si observas, verás que en el caso de que el programa no reciba el nombre del archivo o en el caso de que el operador no tenga privilegios de grabación sobre este archivo, el código de retorno (
$?) sería diferente de cero.
- Grande!, aprendiste bien, pero es bueno dejar claro que el
exit 0, simplemente
exit, o no colocar
exit, producen igualmente un código de retorno (
$?) igual a cero, si el programa fue bien ejecutado. Ahora vamos a hablar sobre las instrucciones de
loop o lazo, pero antes voy a pasar el concepto de bloque de programa.
Hasta ahora ya vimos algunos bloques de programa, como cuando te mostré un ejemplo para hacer un
cd hacia dentro de un directorio, y que era así:
cd lmb 2> /dev/null ||
{
mkdir lmb
cd lmb
}
El fragmento contenido entre las dos llaves (
{}), forma un bloque de comandos. También en este ejercicio que acabamos de ver, en que salvamos el archivo antes de editarlo, existen varios bloque de comandos comprendidos entre los
then y los
fi del
if.
Un bloque de comandos también puede estar dentro de un
case, o entre un
do y un
done.
- Espera ahí Julio, que
do y
done son esos, no me acuerdo de que hayas hablado de ellos y mira que estoy prestando mucha atención...
- Claro, todavía no había hablado de ellos porque no había llegado el momento adecuado. Todas las instrucciones de
loop o lazo, ejecutan los comandos del bloque comprendido entre el
do y el
done.
Comandos de Loop (o lazo)
Las instrucciones de
loop o lazo son el
for, el
while y el
until que pasaré a explicarte una a una a partir de hoy.
El comando for
Si ya estás habituado a programar, con seguridad conoces el comando
for, pero lo que no sabes es que el
for, que es una instrucción intrínseca del
Shell (esto significa que el código fuente del comando es parte del código fuente del
Shell, o sea en buen idioma "programés" es un
built-in), es mucho más poderoso que los semejantes de otras lenguajes.
Vamos a entender su sintaxis, primero en español y después como funciona realmente.
para var en val1 val2 ... valn
haga
cmd1
cmd2
cmdn
hecho
Donde la variable
var asume cada uno de los valores de la lista
val1 val2 ... valn y para cada uno de esos valores ejecuta el bloque de comandos formado por
cmd1,
cmd2 y
cmdn
Ahora que ya vimos el significado de la instrucción en español, veamos la sintaxis correcta:
Primera sintaxis del comando for:
for var in val1 val2 ... valn
do
cmd1
cmd2
cmdn
done
Vamos directo a los ejemplos, a fin de entender el funcionamiento de este comando. Vamos a escribir un
script para listar todos los archivos de nuestro directorio separados por dos puntos, pero mira primero:
$ echo *
ArchDoDOS.txt1 confuso incusu logado musexc musicas musinc muslist
O sea, el
Shell vio el asterisco (
*), lo expandió con el nombre de todos los archivos del directorio y el comando
echo los mostró en la pantalla separados por espacios en blanco. Visto esto vamos a ver como resolver el problema que nos propusimos:
$ cat testefor1
#!/bin/bash
# 1o. Prog didáctico para entender el for
for Arch in *
do
echo -n $Arq: # La opción -n es para no saltar la línea
done
Ahora vamos a ejecutarlo:
$ testefor1
ArchDoDOS.txt1:confuso:incusu:logado:musexc:musicas:musinc:muslist:$
Como viste, el
Shell transformó el asterisco (es odioso ser llamado por un asterisco) en una lista de archivos separados por espacios en blanco. cuando el
for vio aquella lista, se dijo: "Opa!, lista separadas por espacios es mi especialidad!"
El bloque de comandos para ejecutar era solamente el
echo, que con la opción
-n listó la variable
$Arch seguida de dos puntos (
:), sin saltar de línea. El signo de (
$) del final de la línea de ejecución es el
prompt. que permaneció en la misma línea también en función de la opción
-n.
Otro ejemplo simple (por ahora):
$ cat testefor2
#!/bin/bash
# 2o. Prog didáctico para entender el for
for Palabra in Conversa de Bar
do
echo $Palabra
done
Y ejecutando resulta:
$ testefor2
Conversa
de
Bar
Como viste, este ejemplo es tan bobo y simple como el anterior, pero sirve para mostrar el comportamiento básico del
for.
Fíjate en la fuerza del
for: todavía estamos en la primera sintaxis del comando y ya estoy mostrando nuevas formas de usarlo. Allá atrás, te había hablado que el
for usaba listas separadas por espacios en blanco, pero eso es una verdad a medias, era sólo para facilitar la compresión.
En realidad, las listas no son obligatoriamente separadas por espacios, pero antes de seguir, déjame mostrarte como se comporta una variable del sistema llamada
$IFS. Observa su contenido:
$ echo "$IFS" | od -h
0000000 0920 0a0a
0000004
O sea, mandé la variable (protegida de la interpretación del
Shell por las comillas) para un
dump hexadecimal (
od -h) y resultó:
| Contenido de la Variable $IFS |
0a |
<ENTER> |
| Hexadecimal |
Significado |
09 |
<TAB> |
20 |
<ESPACIO> |
Donde el último
0a fue originado por el
<ENTER> dado al final del comando. Para mejorar la explicación, vamos a ver eso de otra forma:
$ echo ":$IFS:" | cat -vet
: ^I$
:$
Presta atención a lo siguiente para entender la construcción del comando cat:

En el comando
cat, la opción
-e representa el
<ENTER> como un signo de pesos (
$) y la opción
-t representa el
<TAB> como un
^I. Usé los dos puntos (
:) para mostrar el inicio y el fin del
echo. y de esta forma, otra vez podemos notar que los tres caracteres están presentes en aquella variable.
Ahora,
IFS significa
Inter Field Separator o, traduciendo, separador entre campos. Una vez entendido eso, puedo afirmar (porque lo voy a probar) que el comando
for no usa listas separadas por espacios en blanco, sino por el contenido de la variable
$IFS, cuyo valor por defecto (
default) son esos caracteres que acabamos de ver. Para comprobarlo, vamos a mostrar un
script que recibe el nombre del artista como parámetro y lista las músicas que este ejecuta, pero primero veremos como está nuestro archivo
musicas:
$ cat musicas
album 1^Artista1~Musica1:Artista2~Musica2
album 2^Artista3~Musica3:Artista4~Musica4
album 3^Artista5~Musica5:Artista6~Musica6
album 4^Artista7~Musica7:Artista1~Musica3
album 5^Artista9~Musica9:Artista10~Musica10
En base a este esquema mostrado arriba fue desarrollado el
script que sigue:
$ cat listartista
#!/bin/bash
# Dado un artista, muestra sus músicas
if [ $# -ne 1 ]
then
echo Usted debería haber pasado un parámetro
exit 1
fi
IFS="
:"
for ArtMus in $(cut -f2 -d^ musicas)
do
echo "$ArtMus" | grep $1 && echo $ArtMus | cut -f2 -d~
done
El
script, como siempre, comienza testando si los parámetros fueron pasados correctamente, en seguida el
IFS fue configurado para
<ENTER> y dos puntos (
:) (como demuestran las comillas en líneas diferentes), porque es él el que separa los bloques
Artistan~Musicam. De esta forma, la variable
$ArtMus irá a recibir cada uno de estos bloques del archivo (observa que el
for ya recibe los registros sin el álbum en virtud del
cut en su línea). En el caso de que encuentre el parámetro (
$1) en el bloque, el segundo
cut listará solamente el nombre de la música. Vamos a ejecutarlo:
$ listartista Artista1
Artista1~Musica1
Musica1
Artista1~Musica3
Musica3
Artista10~Musica10
Musica10
Epa! Pasaron dos cosas no deseadas: los bloques también fueron listados y la
Musica10 también. Además de eso, nuestro archivo de músicas es muy simple, en la vida real, tanto la música como el artista tienen más de un nombre. Suponte que el artista fuera una dupla de música folclórica llamada
Clitandro & Eduviges (no me atrevo ni a dar la idea, por miedo a que se haga realidad

). En este caso el
$1 sería Clitandro y el resto de este lindo nombre sería ignorado en la búsqueda.
Para que eso no ocurriese, debería pasar el nombre del artista entre comillas (
") o alterar
$1 por
$@ (que significa todos los parámetros pasados), que es la mejor solución, pero en este caso tendría que modificar la crítica de los parámetros y el
grep. La nueva crítica no actuaria si yo pasase un parámetro, o
por lo menos un parámetro y en cuanto al
grep, mira lo que resultaría después de la substitución del
$* (que entraría en lugar del
$1) por los parámetros:
echo "$ArtMus" | grep clitandro & eduviges
Lo que resultaría en un error. Lo corretco sería:
echo "$ArtMus" | grep -i "clitandro & eduviges"
Donde fue colocada la opción
-i para que la búsqueda ignorase mayúsculas y minúsculas y las comillas también fueron insertadas para que el nombre del artista fuera visto como una cadena única y monolítica.
Todavia falta arreglar el error de haber listado al
Artista10. Para esto, lo mejor es decirle al
grep que la cadena está en el início ( forma cuya expresión regular es
^) de
$ArtMus y luego después viene una tilde (
~). Es necesario también que se redireccione la salida del grep para
/dev/null para que los bloques no sean listados más . Observa entonces la nueva (y definitiva) cara del programa:
$ cat listartista
#!/bin/bash
# Dado un artista, muestra sus músicas
# versao 2
if [ $# -eq 0 ]
then
echo Usted debería haber pasado un parámetro
exit 1
fi
IFS="
:"
for ArtMus in $(cut -f2 -d^ musicas)
do
echo "$ArtMus" | grep -i "^$@~" > /dev/null && echo $ArtMus | cut -f2 -d~
done
Que ejecutado da:
$ listartista Artista1
Musica1
Musica3
Segunda sintáxis del comando for:
for var
do
cmd1
cmd2
cmdn
done
- Espera ahí!, sin el
in como va a saber que valor asumir?
- Eso mismo, no? Esta construcción a primera vista parece extraña pero es bastante simple. En este caso,
var asumirá uno a uno cada uno de los parámetros pasados para el progama.
Vamos rapidito a los ejemplos para entenderlo mejor. Vamos a hacer un
script que reciba como parámetro una cantidad de músicas y liste sus autores:
$ cat listamusica
#!/bin/bash
# Recibe parte de los nombres de músicas como parámetro y
# lista los intérpretes. Si el nombre es compuesto, debe
# ser pasado entre comillas.
# ex. "No soy tu perrito, no!" "Asadito de Madre"
#
if [ $# -eq 0 ]
then
echo Uso: $0 musica1 [musica2] ... [musican]
exit 1
fi
IFS="
:"
for Musica
do
echo $Musica
Str=$(grep -i "$Musica" musicas) ||
{
echo " No encontrada"
continue
}
for ArtMus in $(echo "$Str" | cut -f2 -d^)
do
echo " $ArtMus" | grep -i "$Musica" | cut -f1 -d~
done
done
De la misma forma que los otros, comenzamos el ejercício con una crítica sobre los parámetros recibidos, en seguida hicimos un
for en que la varible
$Musica recibirá cada uno de los parámetros pasados, colocando en
$Str todos los álbums que contienen las músicas pasadas. En seguida, el otro
for coge cada bloque
Artista~Musica de los registros que están en
$Str y lista cada artista que ejecute aquella música.
Como siempre vamos a ejecutarlo para ver si realmente funciona:
$ listamusica musica3 Musica4 "Yegüita Pocotó"
musica3
Artista3
Artista1
Musica4
Artista4
Yegüita Pocotó
No encontrada
La lista quedó fea porque todavia no sabemos dar formato a la salida, pero cualquier día de estos, cuando sepas posicionar el cursor, hacer negritas, trabajar con colores, etc, haremos esta lista nuevamente usando todas estas perfumerías y entoces quedará bien coqueto.
A esta altura de los acontecimientos debes estar preguntandote: "Y aquél
for tradicional de los otros lenguajes en que sale contando a partir de un número, con un determinado incremento hasta alcanzar una condición?"
Y es ahí donde te respondo: "Yo no te dije que nuestro
for es más completo que los otros?" Para hacer eso existen dos formas:
1 - con la primera sintáxis que vimos, como en los siguientes ejemplos directamente en el
prompt:
$ for i in $(seq 9)
> do
> echo -n "$i "
> done
1 2 3 4 5 6 7 8 9
Aquí, la variable
i asumió los enteros del 1 al 9 generados por el comando
seq y la opción
-n del
echo fue usada para no saltar de línea con cada número listado (me siento ecologicamente correcto por no gastar una cantidad de papel de la revista cuando eso puede ser evitado). Además usando el
for con
seq:
$ for i in $(seq 3 9)
> do
> echo -n "$i "
> done
4 5 6 7 8 9
O todavia en la forma más completa del
seq:
$ for i in $(seq 0 3 9)
> do
> echo -n "$i "
> done
0 3 6 9
2 – La otra forma de hacer lo deseado es con una sintáxis muy parecida al
for del lenguaje C, como veremos a continuación.
Tercera sintáxis del comando for:
for ((var=ini; cond; incr))
do
cmd1
cmd2
cmdn
done
Donde:
var=ini - Significa que la variable
var comenzará a partir de un valor inicial
ini;
cond - Significa que el
loop o lazo del
for será ejecutado en cuanto la
var no cumpla la condición
cond;
incr - Significa el incremento que la variable
var sufrirá en cada pasada del
loop.
Como siempre vamos a los ejemplos y la cosa quedara más fácil:
$ for ((i=1; i<=9; i++))
> do
> echo -n "$i "
> done
1 2 3 4 5 6 7 8 9
En este caso la variable
i partió del valor inicial
1, el bloque de comando (aqui solamente el
echo) será ejecutado en cuanto
i sea menor o igual (
<=) a
9 y el incremento de
i será de
1 a cada pasada del
loop.
Fíjate que en el
for propiamente dicho (y no en el bloque de comandos) no coloqué un signo de pesos (
$) antes del
i, y la notación para incrementar (
i++) es diferente de la que vimos hasta ahora. Esto es porque el uso de paréntesis dobles (así como el comando
let) llama el interpretador aritmético del
Shell, que es más tolerante.
Como me referí al comando
let, y sólo para mostrar como funciona, vamos hacer lo mismo, omitiendo sin embargo, la última parte del
for, pasándola hacia el bloque de comandos, así ademas veras la versatilidad del
for.
$ for ((; i<=9;))
> do
> let i++
> echo -n "$i "
> done
1 2 3 4 5 6 7 8 9
Observa que el incremento desapareció del cuerpo del for y pasó dentro del bloque de comandos, fíjate también que cuando usé el
let, no fue necesario siquiera inicializar la varible
$i. Observa los siguientes comandos escritos directamente en el_prompt_ para mostrar lo que acabo de decir:
$ echo $j
$ let j++
$ echo $j
1
O sea, la variable
$j ni siquiera existía y en el primero
let asumió el valor
0 (cero) para, después del incremento, tener el valor
1.
Fíjate en lo simples que son las cosas:
$ for arq in *
> do
> let i++
> echo "$i -> $Arq"
> done
1 -> ArqDoDOS.txt1
2 -> confuso
3 -> incusu
4 -> listamusica
5 -> listartista
6 -> logado
7 -> musexc
8 -> musicas
9 -> musinc
10 -> muslist
11 -> testefor1
12 -> testefor2
- Y hasta aqui amigo!, tengo la seguridad que hoy tomaste una buena dosis de jarabe del comando
for. Por hoy es suficiente, la próxima vez que nos encontremos hablaremos sobre otras instruciones de
loop, pero me gustaria que hasta entonces, hicieses un pequeño
script para contar la cantidad de palabras de un archivo texto, cuyo nombre sería recibido por parámetro.
OBS: Esa cuenta tiene que ser hecha usando el comando for para que te habitues a su uso. No vale usar o
wc -w.
- Chico! Tráeme, por favor la del estribo!
Y no te olvides, cualquer duda o falta de compañia para tomar una cerveza o hasta para hablar mal de los políticos lo único que tienes que hacer es mandarme un e-mail para
julio.neves@gmail.com. Voy aprovechar también para mandar mi aviso publicitario: puedes decirle a los amigos que quien quiera hacer un curso nota diez de programación en
Shell que mande un e-mail para
julio.neves@uniriotec.br para informarse.
Gracias y hasta la
próxima
--
HumbertoPina - 20 Oct 2006

Copyright © by the contributing authors. All material on this collaboration platform is the property of the contributing authors.
Ideas, requests, problems regarding TWiki-SL?
Send feedback