Programación del Shell Linux

Aquí tenemos un libro libre y completo sobre Shell

La sed del "conocimiento libre" es muy bienvenida.

Comandos Shell Script

Diagrama Sintáctico
Júlio Neves

Júlio Neves
Home | Artículos Português InglesAgradecimientos | Links Amigos

Comprar el libro

Changelogs

  Conversación de Bar 1  

  Conversación de Bar 2  

  Conversación de Bar 3  

  Conversación de Bar 4  

  Conversación de Bar 5  

  Conversación de Bar 6  

  Conversación de Bar 7  

  Conversación de Bar 8  

  Conversación de Bar 9  

  Conversación de Bar 10  

  Conversación de Bar 11  

  Aperitivo  

Conversación de Bar - Parte II

- Mozo! Trae un "chopps" y dos "pastel". Mi amigo hoy no va a beber porque finalmente le está siendo presentado un verdadero sistema operativo y todavia tiene muchas cosas que aprender!

- Y entonces, amigo, estas entendiendo todo lo que te expliqué hasta ahora?

- Entendiendo estoy, pero no vi nada práctico en eso...

- Calma, lo que te dije hasta ahora, sirve como base para lo que ha de venir de aquí en adelante. Vamos a usar estas herramientas que vimos para montar programas estructurados, que el Shell te permite. Entonces verás por qué hasta en la TV hubo un programa llamado "El Shell es el Límite".

- Para comenzar vamos a hablar de los comandos de la familia grep.

- grep? No conozco ningún término en inglés con este nombre...

- Por supuesto, grep es un acrónimo Global Regular Expression Print, que usa expresiones regulares para buscar la ocurrencia de cadenas de caracteres en la entrada definida (has de saber que hay una leyenda sobre como fue bautizado este comando: en el editor de textos "ed", el abuelo del "vim", el comando usado para buscar era g/_expresión regular_/p, en inglés g/_re_/p). Por hablar en expresiones regulares (o regexp), el portal BULMA - Bisoños Usuarios de GNU/Linux de Mallorca y Alrededores tiene todos los enlaces en su página (inclusive tutoriales) que abordan el tema. Si realmente tienes ganas de aprender a programar en Shell, Perl, Python, ... debes leer estos artículos que te ayudarán a resolver lo que nos espera más adelante.

Me quedo con el grep, tú con la gripe

Eso de la gripe es un sólo un decir! Simplemente un pretexto para pedir unas "Caipirinhas" (o caipiriñas, el coctel oficial del carnaval brasileño wink - vea cómo prepararlas). Volviendo a nuestro tema, te dije que el grep busca cadenas de caracteres dentro de una entrada definida, pero en realidad... que viene a ser una "entrada definida"? Bueno, existen varias formas de definir la entrada del comando grep. Veamos: Buscando en un archivo:

$ grep rafael /etc/passwd

buscando en varios archivos:

$ grep grep *.sh

Buscando en la salida de un comando:

$ who | grep Pelegrino

En el 1º ejemplo, el más simple, busqué la palabra rafael en cualquier lugar del archivo /etc/passwd. Si quisiera buscarla como un login name, o sea, solamente en el principio de los registros de este archivo, debería hacer:

$ grep '^rafael' /etc/passwd

Y para que sirve este acento circunflejo y los apostrofes? me vas a preguntar. Si hubieras leído los artículos anteriores sobre expresiones regulares que te dije, sabrías que el circunflejo (^) sirve para limitar la búsqueda al inicio de cada línea, y los apostrofes (') sirven para que el Shell no interprete este circunflejo, dejándolo pasar intacto para el comando grep.

Mira que bien! El grep acepta como entrada, la salida de otro comando redireccionado por un pipe (esto es muy común en Shell y es un tremendo acelerador de la ejecución de comandos, ya que actúa como si la salida de un programa fuera guardada en disco y el segundo programa leyera este archivo generado), de esta forma, en el 3º ejemplo, el comando who listó las personas "logadas" en la misma máquina que tú (no te olvides jamás: el Linux es multiusuario) y el grep fue usado para verificar si Pelegrino estaba trabajando o simplemente "haciendo sebo".

La familia grep

Este comando grep es muy conocido, pues es usado con mucha frecuencia. Algo que muchas personas desconocen es que existen tres comandos en la familia grep, que son:

  • grep
  • egrep
  • fgrep

Las principales diferencias entre los 3 son:

  • El grep puede o no, usar expresiones regulares simples, sin embargo en caso de no usarlas, el fgrep es mejor, por ser más rápido;
  • El egrep ("e" de extended, extendido) es muy poderoso en el uso de expresiones regulares. Por ser el más lento de la familia, solo debe ser usado cuando sea necesario la construcción de una expresión regular que no sea aceptada por el grep;
  • El fgrep ("f" de fast, rápido, o de "file", archivo) como el nombre dice, es el rapidito de la familia, ejecuta el servicio de forma muy veloz (a veces es cerca de 30% más rápido que el grep y 50% más que el egrep), sin embargo no permite el uso de expresiones regulares en la búsqueda.
Pinguim com placa de atenção (em espanhol) Todo lo que dije arriba sobre la velocidad, solamente se aplica a la familia de comandos grep del Unix. En Linux el grep es siempre más veloz, ya que los otros dos (fgrep y egrep) son scripts en Shell que llaman al primero y, ya me estoy adelantando, no me gusta nada esta solución.

- Ahora que ya conoces las diferencias entre los miembros de la familia, dime: que te parecen los tres ejemplos que te dí antes de las explicaciones?

- Me pareció que el fgrep resolvería tu problema de forma más veloz que el grep.

- Perfecto! Estoy notando que estás bien atento! Estás entendiendo lo que te estoy explicando! Entonces vamos a ver más ejemplos, para aclarar de una vez por todas las diferencias del uso de los miembros de esta familia.

Ejemplos

Yo sé que en un archivo existe un texto hablando sobre Linux solo que no sé si está escrito con L mayúscula o l minúscula. Puedo hacer la búsqueda de dos formas:

$ egrep (Linux | linux) archivo.txt

o

$ grep [Ll]inux archivo.txt

En el primer caso, la expresión regular compleja "(Linux | linux)" usa los paréntesis para agrupar las opciones y la barra vertical (|) como un "o" lógico, o sea, estoy buscando Linux o linux.

En el segundo, la expresión regular [Ll]inux significa: comenzando por L o l seguido de inux. Como esta expresión es más simple, el grep consigue resolverla, por lo tanto creo que es mejor usar la segunda forma, ya que el egrep haría la búsqueda más lenta.

Otro ejemplo. Para listar todos los subdirectorios del directorio actual, basta hacer:

$ ls -l | grep '^d' drwxr-xr-x 3 root root 4096 Dec 18 2000 doc drwxr-xr-x 11 root root 4096 Jul 13 18:58 freeciv drwxr-xr-x 3 root root 4096 Oct 17 2000 gimp drwxr-xr-x 3 root root 4096 Aug 8 2000 gnome drwxr-xr-x 2 root root 4096 Aug 8 2000 idl drwxrwxr-x 14 root root 4096 Jul 13 18:58 locale drwxrwxr-x 12 root root 4096 Jan 14 2000 lyx drwxrwxr-x 3 root root 4096 Jan 17 2000 pixmaps drwxr-xr-x 3 root root 4096 Jul 2 20:30 scribus drwxrwxr-x 3 root root 4096 Jan 17 2000 sounds drwxr-xr-x 3 root root 4096 Dec 18 2000 xine

En el ejemplo que acabamos de ver, el circunflejo (^) sirvió para limitar la busqueda a la primera posición de la salida del ls detallado. los apostrofes fueron colocados para que el Shell no "viera" el circunflejo (^).

Vamos a ver otro. Sabemos que las cuatro primeras posiciones posibles de un ls -l de un archivo común (archivo común!, No directorio, ni link, ni ...) deben ser:

Posición  1ª   2ª   3ª   4ª 
      -
 Valores Posibles  - r w x
  - -   s (suid) 

Así, para descubrir todos los archivos ejecutables en un determinado directorio debería hacer:

$ ls -la | egrep '^-..(x|s)' -rwxr-xr-x 1 root root 2875 Jun 18 19:38 rc -rwxr-xr-x 1 root root 857 Aug 9 22:03 rc.local -rwxr-xr-x 1 root root 18453 Jul 6 17:28 rc.sysinit

Donde nuevamente usamos el circunflejo (^) para limitar la búsqueda al inicio de cada línea, entonces las líneas listadas serán las que comienzan por un trazo (-), seguido de cualquier cosa (el punto cuando es usado como una expresión regular significa cualquier cosa), nuevamente seguido de cualquier cosa, y siguiendo un x o un s.

Obtendríamos el mismo resultado si hiciéramos:

$ ls -la | grep '^-..[xs]'

y agilizaríamos la búsqueda.

Vamos a montar una "CDteca"

Vamos a comenzar a desarrollar programas, me parece que montar un banco de datos de músicas es muy útil y didáctico (y además práctico, en estos tiempos de downloads de mp3 y "quemadores" de CDs). No te olvides que, de la misma forma vamos a desarrollar una cantidad de programas para organizar tus CDs de música, con pequeñas adaptaciones, puedes hacer lo mismo con los CDs de software que vienen con la Linux Magazine y otros que compres o quemes, si compartes este banco de software para todos los que trabajan contigo (el Linux es multiusuario, y como tal debe ser explotado), ganarás muchos puntos con tu adorado jefe.

- Un momento! De donde voy la recibir los datos de los CDs?

- Inicialmente, te voy a mostrar como tu programa puede recibir parámetros de quién lo esté ejecutando y en breve, te enseñaré a leer los datos por la pantalla o de un archivo.

Pasando parámetros

El visual del archivo músicas será el siguiente:

     nombre del álbum^intérprete1~nombre de la música1:..:intérprete~nombre de la música

o sea, el nombre del álbum será separado por un circunflejo (^) del resto del registro, que está formado por diversos grupos compuestos por el intérprete de cada música del CD y la respectiva música interpretada. Estos grupos son separados entre sí por dos-puntos (:) e internamente, el intérprete será separado por una tilde (~) del nombre de la música.

Escribiré un programa llamado musinc, que incluirá registros en mi archivo músicas. Pasaré el contenido de cada álbum como parâmetro en la llamada del programa de la siguiente forma:

$ musinc "álbum^interprete~música:interprete~música:..."

De esta forma el programa musinc estará recibiendo los datos de cada álbum como si fuera una variable. La única diferencia entre un parámetro recibido y una variable es que los primeros reciben nombres numéricos (nombre numérico suena algo raro, no?). Lo que quise decir es que sus nombres son formados por un y solamente un algarismo), el sea $1, $2, $3, ..., $9. Vamos, antes de todo, hacer un test:

Ejemplos

$ cat test #!/bin/bash # Programa para verificar el pasaje de parámetros echo "1o. param -> $1" echo "2o. param -> $2" echo "3o. param -> $3"

Vamos a ejecutarlo:

$ test pasando parámetros para verificar bash: test: cannot execute

Opa! Me olvidé de hacerlo ejecutable. Voy a hacerlo de forma que permita que todos puedan ejecutarlo y en seguida voy a testearlo:

$ chmod 755 test $ test pasando parámetros para verificar 1o. param -> pasando 2o. param -> parámetros 3o. param -> para

Repara que la palabra verificar, que sería el cuarto parámetro, no fue listada. Esto sucedió justamente porque el programa test solo listaba los tres primeros parámetros. Vamos ejecutarlo de otra forma:

$ test "pasando parámetros" para verificar 1o. param -> pasando parámetros 2o. param -> para 3o. param -> verificar

Las comillas no dejaron que el Shell viese el espacio en blanco entre las palabras y las consideró como un único parámetro.

Observaciones sobre parámetros

Ya que estamos hablando de pasar parámetros observa bien lo siguiente:

Significado de las Principales Variables Referentes a los Parámetros
$*   Contiene el conjunto de todos los parámetros (muy parecido con $@)  
  Variable     Significado  
$0   Contiene el nombre del programa  
$#   Contiene la cuantidad de parámetros pasados  

Ejemplos

Vamos a alterar el programa test para usar las variables que acabamos de ver. Vamos hacerlo así:

$ cat test #!/bin/bash # Programa para verifivar el paso de parámetros (2a. Versao) echo El programa $0 recibió $# parámetros echo "1o. param -> $1" echo "2o. param -> $2" echo "3o. param -> $3" echo Todos de una sola \"bolada\": $*

Repare que antes de las comillas usé una barra invertida para esconderlas de la interpretación del Shell (si no usase las contrabarras las comillas no aparecerian). Vamos a ejecutarlo:

$ test pasando parámetros para verificar El programa test recibió 4 parámetros 1o. param -> pasando 2o. param -> parámetros 3o. param -> para Todos de una sola "bolada": pasando parámetros para verificar

Como ya dije, los parámetros reciben números de 1 a 9, pero eso no significa que no puedo usar más de 9 parámetros, significa solamente que solo puedo direccionar 9. Vamos a verificar eso:

Ejemplo:

$ cat test #!/bin/bash # Programa para verificar el pasaje de parámetros (3a. Versión) echo El programa $0 recebió $# parámetros echo "11o. param -> $11" shift echo "2o. param -> $1" shift 2 echo "4o. Param -> $1"

Vamos a ejecutarlo:

$ test pasando parámetros para verificar El programa test recibió 4 parámetros que son: 11o. param -> pasando1 2o. param -> parámetros 4o. param -> verificar

Dos cosas muy interesantes en este script:

  1. Para mostrar que los nombres de los parámetros varían de $1 a $9 hice un echo $11 y que pasó? El Shell interpretó como que era $1 seguido del algarismo 1 y listó pasando1;
  2. El comando shift cuya sintáxis es shift n, pudiendo el n asumir cualquier valor numérico (sin embargo su default es 1, como en el ejemplo dado), desprecia los n primeros parámetros, devolviendo el parámetro de orden n+1, el primero o sea, el $1.

Bueno, ahora que ya sabes más sobre pasar parámetros que yo mismo, vamos a volver a nuestra "CDteca" para hacer el script que incluira los CDs en mi banco llamado musicas. El programa es muy simple (como todo en Shell) y voy a listarlo para que lo veas:

Ejemplos

$ cat musinc #!/bin/bash # Incluye CDs (versión 1) # echo $1 >> musicas

El script es fácil y funcional, me limito a añadir al final del archivo musicas el parámetro recibido. Vamos a incluir 3 álbumes para ver si funciona (y para no hacerlo muy aburrido, voy a suponer que en cada CD existem solamente 2 músicas):

$ musinc "album 3^Artista5~Musica5:Artista6~Musica5" $ musinc "album 1^Artista1~Musica1:Artista2~Musica2" $ musinc "album 2^Artista3~Musica3:Artista4~Musica4"

Muestro ahora el contenido de musicas.

$ cat musicas album 3^Artista5~Musica5:Artista6~Musica6 album 1^Artista1~Musica1:Artista2~Musica2 album 2^Artista3~Musica3:Artista4~Musica4

No es tan funcional como esperaba que quedase... podía haber quedado mejor. Los álbumes están fuera de orden, dificultando la búsqueda. Vamos a alterar nuestro script y después a probarlo nuevamente:

$ cat musinc #!/bin/bash # Incluye CDs (versión 2) # echo $1 >> musicas sort musicas -o musicas

Vamos a incluir un álbum más:

$ musinc "album 4^Artista7~Musica7:Artista8~Musica8"

Ahora vamos a ver lo que pasó con el archivo musicas:

$ cat musicas album 1^Artista1~Musica1:Artista2~Musica2 album 2^Artista3~Musica3:Artista4~Musica4 album 3^Artista5~Musica5:Artista6~Musica5 album 4^Artista7~Musica7:Artista8~Musica8

Simplemente incluí una línea que clasifica el archivo musicas dándole la salida sobre si mismo (para eso sirve la opción -o), después de que cada álbum fue incluído.

Opa! Ahora está quedando bien y casi funcional. Pero atención, no te desesperes! Esta no es la versión final. El programa quedará mucho mejor y más amigable, en una nueva versión que haremos después que aprendamos la adquirir los datos de la pantalla y a formatear la entrada

Ejemplos

Usar el comando cat para listar no es una buena idea, vamos a hacer un programa llamado=muslist=, para listar un álbum cuyo nombre será pasado como un parámetro:

$ cat muslist #!/bin/bash # Consulta CDs (versión 1) # grep $1 musicas

Vamos a ejecutarlo, buscando el album 2. Como ya vimos anteriormente, para pasar la cadena de caracteres album 2 es necesario protegerla de la interpretación del Shell, así él no la interpreta como dos parámetros separados. Vamos a hacerlo de la siguiente forma:

$ muslist "álbum 2" grep: can't open 2 musicas: album 1^Artista1~Musica1:Artista2~Musica2 musicas: album 2^Artista3~Musica3:Artista4~Musica4 musicas: album 3^Artista5~Musica5:Artista6~Musica6 musicas: album 4^Artista7~Musica7:Artista8~Musica8

Que desorden! Donde está el error?. Tuve buen cuidado de colocar el parámetro pasado entre comillas, para que el Shell no lo diviera en dos!

Si, pero advierte ahora como el grep está siendo ejecutado:

     grep $1 musicas

Aunque coloque álbum 2 entre comillas, para que fuera visto como un único parámetro, cuando el $1 fue pasado por el Shell hacia el comando grep, lo transformó en dos argumentos. Así, el contenido final de la línea que el comando grep ejecutó fue el siguiente:

     grep album 2 musicas

Como la sintáxis del grep es:

    grep <cadena de caracteres> [arch1, arch2, ..., archn]

el grep entendió que debería recuperar la cadena de caracteres album en los archivos 2 y musicas, Al no existir el archivo 2 generó el error, y como encontro la palabra album en todos los registros de musicas, los listo todos.

Pinguim com placa de dica Siempre que la cadena de caracteres a ser pasada hacia el comando grep posea blancos o TAB, y lo mismo que dentro de variables, colóquela siempre entre comillas para evitar que las palabras después del primer espacio en blanco o TAB sean interpretadas como nombres de archivos.

Por otro lado, es mejor ignorar las mayúsculas y minúsculas en la búsqueda. Resolveríamos los dos problemas si el programa tuviera la siguiente forma:

$ cat muslist #!/bin/bash # Consulta CDs (versión 2) # grep -i "$1" musicas

En este caso, usamos la opción -i del grep, que como vimos, sirve para ignorar mayúsculas y minúsculas, y colocamos el $1 entre comillas, para que el grep continue viendo la cadena de caracteres resultante de la expansión de la línea por el Shell como un único argumento de búsqueda.

$ muslist "album 2" album2^Artista3~Musica3:Artista4~Musica4

Ahora, nota que el grep localiza la cadena buscada en cualquier lugar del registro, entonces de la forma que estamos lo haciendo, podemos buscar por álbum, por música, por intérprete o hasta por un pedazo de cualquiera de estos. Cuando conozcamos los comandos condicionales, montaremos una nueva versión de muslist que permitirá especificar por que campo buscar.

Ahora me vas a decir:

- Si, todo bien, pero es muy tedioso tener que colocar el argumento de búsqueda entre comillas cuando tengo que pasar el nombre del álbum. Esta forma no es nada amigable!

- Tienes toda la razón, y es por eso que te voy a mostrar otra forma de hacer lo que me pediste:

$ cat muslist #!/bin/bash # Consulta CDs (versión 3) # grep -i "$*" musicas $ muslist album 2 album 2^Artista3~Musica3:Artista4~Musica4

De esta forma, el $*, que significa todos los parámetros, será substituído por la cadena album 2 (de acuerdo con el ejemplo anterior), haciendo lo que tu querias.

No te olvides que el problema del Shell no es si él puede o no hacer una determinada cosa. El problema es decidir cuál es la mejor forma de hacerla, ya que para realizar cualquier tarea, la cantidad de opciones es enorme.

Recuerdas aquel dia de verano fuiste a la playa?, olvidaste el CD en el automóbil, y entonces aquel "solecito" de 40 grados dobló tu CD y ahora necesitas una herramienta para borrarlo del banco de datos. No hay ningún problema, vamos a desarrollar un script llamado musexc, para excluir estos CDs.

Antes de desarrollar el programa te quiero presentar una opción bastante útil de la familia de comandos grep. Es la opción -v, que cuando es usada, lista todos los registros de la entrada, excepto el(los) localizado(s) por el comando. Veamos:

Ejemplos

$ grep -v "album 2" musicas album 1^Artista1~Musica1:Artista2~Musica2 album 3^Artista5~Musica5:Artista6~Musica6 album 4^Artista7~Musica7:Artista8~Musica8

De acuerdo con lo que te expliqué antes, el grep del exemplo listó todos los registros de músicas excepto los referentes al album 2, porque atendía al argumento del comando. Estamos entonces preparados para desarrollar un script para retirar aquél CD doblado de tu "CDteca". Este script tiene la forma siguiente:

$ cat musexc #!/bin/bash # Borra CDs (versión 1) # grep -v "$1" musicas > /tmp/mus$$ mv -f /tmp/mus$$ musicas

En la primera línea mandé hacia /tmp/mus$$ el archivo musicas, sin los registros que tuviesen la consulta hecha por el comando grep. En seguida, moví (que, en realidad, equivale a renombrarlo) /tmp/mus$$ al antiguo musicas.

Usé el archivo /tmp/mus$$ como archivo de trabajo, porque como ya habia citado en el artículo anterior, el $$ contiene el PID (Process Identification o identificación del proceso) y de esta forma cada uno que edite el archivo musicas lo hará en un archivo de trabajo diferente, de esta forma evitamos choques en su uso.

- Y entonces, amigo, estos programas que hicimos hasta aquí son muy rústicos en virtud de la falta de herramientas que todavia tenemos. Pero están bien, mientras me tomo otro "chopp", puedes ir para casa a praticar en los ejemplos dados porque, te prometo, llegaremos a desarrollar un sistema bien bonito para el control de tus CDs.

- Cuando nos encontremos la próxima vez, te voy a enseñar como funcionan los comandos condicionales y mejoraremos otro poco estos scripts.

- Por hoy es suficiente! Ya hablé demasiado y necesito mojar las palabras porque estoy con la garganta seca!

- Mozo! Otro sin y espuma!

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ém 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 - 13 Sep 2006

Licencia Creative Commons - Reconocimiento y no comercial (CC) 2009 Por Visitantes del Bar de Júlio Neves.

Todo el contenido de esta página puede ser usada de acuerdo a la Creative Commons License: Atribuição-UsoNãoComercial-PermanênciaDaLicença.