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 X



-Que hay amigo, te lo puse mas fácil, verdad? Un ejercicio muy simple...

- Si, pero en los tests que hice y de acuerdo con lo que me enseñaste sobre substitución de parámetros, me pareció que debería hacer otras alteraciones en las funciones que creamos, para dejarlas de uso más general como me dijiste que todas las funciones deberían de ser, quieres ver?

- Claro, si te pedí hacerlas es porque estoy con ganas de verte aprender, pero alto! dame un momento!

- Mozo! Trae dos, uno sin espuma!

- Anda, enseñame lo que hiciste.

- Bien, además de lo que me pediste, me fije que el programa que llamaba la función, tendría que tener previamente definida la línea en que sería dado el mensaje y la cantidad de columnas. Lo que hice fue incluir dos líneas - en las cuales emplee sustitución de parámetros - y en caso de que una de estas variables no fuese introducida, la propia función la generaría. La línea del mensaje estaría tres líneas encima del final de la pantalla y el total de columnas sería obtenido por el comando tput cols. Mira como quedó:

$ cat pergunta.func # La función recibe 3 parámetros en el siguiente orden: # $1 - Mensaje a ser dado en pantalla # $2 - Valor a ser aceptado como respuesta default # $3 - Otro valor aceptado # Suponiendo que $1=Acepta?, $2=s y $3=n, la línea # abajo colocaría en Msj el valor "Acepta? (S/n)" TotCols=${TotCols:-$(tput cols)} # Si no estaba definido, ahora lo está LineaMesj=${LineaMesj:-$(($(tput lines)-3))} # Idem Msj="$1 (`echo $2 | tr a-z A-Z`/`echo $3 | tr A-Z a-z`)" TamMsj=${#Msj} Col=$(((TotCols - TamMsj) / 2)) # Para centrar Msj en la línea tput cup $LineaMesj $Col read -n1 -p "$Msj " SN SN=${SN:-$2} # Si vacio coloca default en SN SN=$(echo $SN | tr A-Z a-z) # La salida de SN será en minúscula tput cup $LineaMesj $Col; tput el # Borra msj de pantalla

- Me gustó, te anticipaste a lo que te iba a pedir. Solamente para cerrar esta conversación de sustitución de parámetros, fíjate que la legibilidad es horrible, pero la optimización, o sea, la velocidad de ejecución, está óptima. Como las funciones son cosas muy personales, ya que cada uno usa las suyas, y casi no se les da mantenimiento, yo siempre opto por la optimización.

- Hoy vamos a salir de aquel aburrimiento que fue nuestra última conversación y volveremos a la lógica saliendo de la memorización, pero te vuelvo a recordar, todo lo que te mostré la otra vez aquí en el Bar, es válido y de gran ayuda, guarda aquellas servilletas que escribimos que tarde o temprano te van a ser muy útiles.

El comando eval

- Te voy a dar un problema que dudo que resuelvas:

$ var1=3 $ var2=var1

- Te dí estas dos variables, y quiero que me digas como puedo, solamente refiriéndome a $var2, listar el valor de $var1 (3).

- Ah! eso es fácil, es sólo hacer:

    echo $`echo $var2`

- Fíjate que coloqué el echo $var2 entre comillas (`), que de esta forma tendrá prioridad de ejecución y resultará en var1, montando echo$var1 que producirá 3...

- A sí? Entonces ejecutalo a ver si está correcto.

$ echo $`echo $var2` $var1

- Eh! Que pasó? Mi razonamiento parecía bastante lógico...

- Tu razonamiento realmente fue lógico, el problema es que te olvidaste de una de las primeras cosas de que te hablé aquí en el Bar y voy a repetir. El Shell usa el siguiente orden para resolver una línea de comandos:

  • Resuelve los redireccionamentos;
  • Substituye las variables por sus valores;
  • Resuelve y substituye los meta caracteres;
  • Pasa la línea ya toda masticada, para ejecución.

De esta forma, cuando llegó a la fase de resolución de variables, que como ya dije es anterior a la ejecución, la única variable existente era $var2 y por eso tu solución produjo como salida $var1. El comando echo identificó eso como una cadena y no como una variable.

Problemas de este tipo son relativamente frecuentes y serían insolubles en caso de que no existiese la instrucción eval, cuya sintaxis es:

    eval cmd

Donde cmd es una línea de comando cualquiera que se podría inclusive ejecutar directamente en el prompt del terminal. Cuando pones el eval al principio, lo que ocurre es que el Shell trata cmd como si sus datos fueran parámetros del eval y enseguida el eval ejecuta la línea recibida, sometiéndola al Shell, dando entonces en la práctica dos pasadas en cmd.

De esta forma si ejecutásemos el comando que propusiste, colocando el eval en su comienzo, tendríamos la salida esperada, mira sino:

$ eval echo $`echo $var2` 3

Este ejemplo también podría haber sido hecho de la siguiente manera:

$ eval echo \$$var2 3

En la primera pasada la barra invertida (\) sería retirada y $var2 resuelto, produciendo var1. Para la segunda pasada habría sobrado echo $var1, que produciría el resultado esperado.

Ahora voy a colocar un comando dentro de var2:

$ var2=ls

Voy a ejecutar:

$ $var2 10porpag1.sh alo2.sh listamusica logaute.sh 10porpag2.sh confuso listartista mandamsj.func 10porpag3.sh contpal.sh listartista3 monbg.sh alo1.sh incusu logado

Ahora vamos a colocar en var2 el siguiente: ls $var1; y en var1 vamos a colocar l*, veamos:

$ var2='ls $var1' $ var1='l*' $ $var2 ls: $var1: No such file or directory $ eval $var2 listamusica listartista listartista3 logado logaute.sh

Nuevamente, al momento de la sustitución de las variables, $var1 todavía no se había presentado al Shell para ser resuelta, por eso sólo nos queda ejecutar el comando eval para dar las dos pasadas necesarias.

Una vez un colega de una excelente lista sobre Shell Script, presentó una duda: quería hacer un menú que numerase y listase todos los archivos con extensión .sh y cuando el operador escogiese una opción, el programa correspondiente sería ejecutado. Mi propuesta fue la siguiente:

$ cat fazmenu #!/bin/bash # # Lista numerando los programas con extensión .sh en # directorio actual y ejecuta el escogido por el operador # clear; i=1 printf "%11s\t%s\n\n" Opción Programa CASE='case $opt in' for arq in *.sh do printf "\t%03d\t%s\n" $i $arq CASE="$CASE "$(printf "%03d)\t %s;;" $i $arq) i=$((i+1)) done CASE="$CASE *) . error;; esac" read -n3 -p "Introduce la opción deseada: " opt echo eval "$CASE"

Parece complicado porque usé mucho el printf para formatear la pantalla, pero es bastante simple, vamos a entenderlo: el primer printf fue colocado para hacer el encabezado y en seguida comencé a montar dinámicamente la variable $CASE, sobre la cual al final será hecho un eval para la ejecución del programa escogido. Observa sin embargo que dentro del loop del for existen dos printf: el primero sirve para formatear la pantalla y el segundo para montar el case (si antes del comando read colocas una línea echo "$CASE", verás que el comando case montado dentro de la variable está todo indentado. Una pasada, verdad? :). En la salida del for, fue agregada una línea a la variable $CASE, para que en el caso de que se haga una opción no válida, sea ejecutada una función externa para dar mensajes de error.

Vamos a ejecutarlo para ver la salida generada:

$ fazmenu.sh Opción Programa

001 10porpag1.sh 002 10porpag2.sh 003 10porpag3.sh 004 alo1.sh 005 alo2.sh 006 contpal.sh 007 fazmenu.sh 008 logaute.sh 009 monbg.sh 010 readpipe.sh 011 redirread.sh Introduce la opción deseada:

En este programa sería interesante tener una opción de escape, y para eso sería necesario la inclusión de una línea después del loop de montaje de la pantalla y alterar la línea en la cual hacemos la atribución final del valor de la variable $CASE. Veamos como quedaría:

$ cat fazmenu #!/bin/bash # # Lista numerando los programas con extensión .sh en # directorio actual y ejecuta el escogido por el operador # clear; i=1 printf "%11s\t%s\n\n" Opción Programa CASE='case $opt in' for arq in *.sh do printf "\t%03d\t%s\n" $i $arq CASE="$CASE "$(printf "%03d)\t %s;;" $i $arq) i=$((i+1)) done printf "\t%d\t%s\n\n" 999 "Fin del programa" # línea incluida CASE="$CASE 999) exit;; # línea alterada *) ./error;; esac" read -n3 -p "Introduce la opción deseada: " opt echo eval "$CASE"

Señales de Procesos

Existe en Linux una cosa llamada señal (signal). Existen diversas señales que pueden ser mandadas para (o generados por) procesos en ejecución. Vamos de aqui en adelante a dar una ojeada a las señales enviadas hacia los procesos y más adelante vamos a dar una pasada rápida por las señales generados por procesos.

señales asesinas

Para mandar una señal a un proceso, usamos normalmente el comando kill, cuya sintáxis es:

    kill -sig PID

Donde PID es el identificador del processo (Process IDentification o Process ID). Además del comando kill, algunas secuencias de teclas también pueden generar sig. La tabla siguiente muestra las señales más importantes para monitorear:

Señales Más Importantes
 15   SIGTERM  Cuando recibe un kill o kill -TERM 
Señal  Generado por:
0 EXIT  Fin normal de programa
1 SIGHUP  Cuando recibe un kill -HUP
2 SIGINT  Interrupción por teclado (<CTRL+C>)
3 SIGQUIT   Interrupción por teclado (<CTRL+\>)

Además de estas señales, existe el tan abusado -9 o SIGKILL que, para el proceso que lo está recibiendo, equivale a meter el dedo en el botón de apagar el computador, lo que es altamente indeseable ya que muchos programas necesitan "limpiar el medio campo" a su término. Si el final ocurre de forma prevista, o sea si tiene un final normal, es muy fácil de hacer esta limpeza, sin embargo si el programa tiene un final brusco pueden ocurrir muchas cosas:

  • Es posible que en un determinado espacio de tiempo, el computador esté lleno de archivos de trabajo inútiles
  • El procesador podrá quedar lleno de procesos zombies y defuncts generados por procesos hijos que perdieron los procesos padres;
  • Es necesario liberar sockets abiertos para no dejar los clientes congelados;
  • Tus bancos de datos podrán quedar corruptos porque los sistemas gestores de bancos de datos necesitan de un tiempo para grabar sus buffers en disco (commit).

En fin, existen mil razones para no usar un kill con la señal -9 y para monitorizar las terminaciones anormales de programas.

El trap no atrapa

Para hacer el control de procesos descripto antes, existe el comando trap cuya sintáxis es:

    trap "cmd1; cmd2; cmdn" S1 S2 ... SN

o

    trap 'cmd1; cmd2; cmdn' S1 S2 ... SN

Donde los comandos cmd1, cmd2, cmdn serán ejecutados en caso de que el programa reciba las señales S1 S2 ... SN.

Las comillas (") o los apóstrofes (') sólo son necesarias en el caso de que el trap posea más de un comando cmd asociado. Cada uno de los cmd puede también ser una función interna, una externa u otro script.

Para entender el uso de las comillas (") y los apóstrofes (') vamos a recurrir a un ejemplo que trata un fragmento de un script que hace un ftp hacia una máquina remota ($RemoComp), en la cual el usuário es $Fulano, su contraseña es $Secreto y va a transmitir el archivo contenido en $Arq. Supon todavia que estas cuatro variables fueron recibidas en una rutina anterior de lectura y que este script es muy usado por diversas pesonas de la instalación. Veamos este trozo del código:

ftp -ivn $RemoComp << FimFTP >> /tmp/$$ 2>> /tmp/$$
    user $Fulano $Secreto
    binary
    get $Arq
FimFTP

Observa que, tanto las salidas de los diálogos del ftp, como los errores encontrados, están siendo redireccionados para /tmp/$$, lo que es una construcción bastante normal para archivos temporarios usados en scripts con más de un usuário, porque $$ es la variable que contiene el número de proceso (PID), que es único, y con este tipo de construcción se evita que dos o más usuários disputen la posesión y los derechos sobre el archivo.

En el caso de que este ftp sea interrumpido por un kill o un <CTRL+C>, con toda seguridad dejará basura en el disco. Es exactamente esta la forma más frecuente de usar el comando trap. Como esto es un trozo de un script, debemos hacer, al empezar y como uno de sus primeros comandos:

    trap "rm -f /tmp/$$ ; exit" 0 1 2 3 15

Así en el caso de que hubiese una interrupción brusca (señales 1, 2, 3 o 15), antes de que el programa finalize (en el exit dentro del comando trap), o un fin normal (señal 0), el archivo /tmp/$$ seria borrado del disco.

En el caso de que en la línea de comandos del trap no tuviese la instrucción exit, al final de la ejecución de esta línea el flujo del programa volvería al punto en que estaba cuando recibió la señal que originó la ejecución de este trap.

Este trap podria ser subdividido, quedando de la siguiente forma:

    trap "rm -f /tmp/$$" 0
    trap "exit" 1 2 3 15

Así al recibir una de las señales el programa terminaría, y al terminar, generaría una señal 0, que borraría el archivo. Si la finalización es normal, la señal también será generada y el rm será ejecutado.

Observa también que el Shell analiza la línea de comandos, una vez cuando el trap es interpretado (y es por eso que es usual colocarlo al inicio del programa) y nuevamente cuando se recibe alguna de las señales listadas. Entonces, en el último ejemplo, el valor de $$ será sustituído en el momento que el comando trap es leído por primera vez, ya que las comillas (") no protegen el signo de pesos ($) de la interpretación del Shell.

Si deseas que la sustitución sea realizada solamente en el momento de ser recibida la señal, el comando debería estar colocado entre apóstrofes ('). Así, en la primera interpretación del trap, el Shell no vería el signo de pesos ($), sin embargo los apóstrofes (') serian retirados y finalmente el Shell podría sustituir el valor de la variable. En este caso, la línea quedaria de la siguiente manera:

    trap 'rm -f /tmp/$$ ; exit' 0 1 2 3 15

Suponte dos casos: tu tienes dos scripts que llamaremos script1, cuya primera línea será un trap y script2, siendo este último ejecutado por una llamada del primero, y por ser dos procesos diferentes, tendrán dos PID distintos.

  • 1º Caso: El ftp se encuentra en script1
    En este caso, el argumento del comando trap debería estar entre comillas (") porque si ocurriese una interrupcción (<CTRL+C> o <CTRL+\>) en el script2, la línea sólo seria interpretada en este momento y el PID del script2 seria diferente del encontrado en /tmp/$$ (no te olvides que $$ es la variable que contiene el PID del proceso activo);

  • 2º Caso: El ftp anterior se encuentra en script2
    En este caso, el argumento del comando trap debería estar entre apóstrofes ('), pues en el caso de que la interrupción se diera durante la ejecución del script1, el archivo no habría sido creado, en el caso de que ocurriera durante la ejecución del script2, el valor de $$ sería el PID de este processo, que coincidiría con el de /tmp/$$.

Cuando se ejecuta el comando trap sin argumentos, lista las señales que están siendo monitoreadas en el ambiente, así como la línea de comando que será ejecutada cuando tales señales sean recibidas.

Si la línea de comandos del trap es nula (o sea vacía), esto significa que las señales especificadas deben ser ignoradas cuando sean recibidas. Por ejemplo, el comando:

    trap "" 2

Especifica que la señal de interrupción (<CTRL+C>) debe ser ignorada. En ese caso no se desea que la ejecución sea interrumpida. En el último ejemplo fíjate que el primer argumento debe ser especificado para que la señal sea ignorada, y no es equivalente a escribir lo siguiente, cuya finalidad es de retornar la señal 2 a su estado patrón (default):

    trap 2

Si se ignora una señal, todos los Subshells ignoraran esta señal. Por lo tanto, si tu especificas que acción debe ser tomada cuando se reciba una señal, entonces todos los Subshells también tomaran la misma acción cuando reciban esta señal, o sea, las señales son automáticamente exportadas. Para la señal que hemos mostrado (señal 2), significa que los Subshells serán finalizados.

Suponte que ejecutes el comando:

    trap "" 2

y entonces ejecutes un Subshell, que volverá a ejecutar otro script como un Subshell. Si se generase una señal de interrupción, esta no tendrá efecto sobre el Shell principal ni sobre los Subshell por él llamados, ya que todos ellos ignorarán la señal.

Otra forma de restaurar una señal a su patrón (default) es haciendo:

    trap - señal

En korn shell (ksh) no existe la opción -s del comando read para leer una señal. Lo que acostumbramos hacer es usar el comando stty con la opción -echo que inhibe la escritura en pantalla hasta que se encuentre un stty echo para restaurar esta escritura. Entonces, si estamos usando el interprete ksh, la lectura de la señal sería hecha de la siguiente forma:

    echo -n "Señal: "
    stty -echo
    read Señal
    stty echo

El problema en este tipo de construcción es que en el caso de que el operador no supiese la señal, probablemente haría un <CTRL+C> o un <CTRL+\> durante la instrucción read para detener el programa y en el caso de que actúe así, cualquier cosa que escribiese no aparecería en la pantalla del terminal. Para evitar que eso pase, lo mejor a hacer es:

    echo -n "Señal: "
    trap "stty echo
          exit" 2 3
    stty -echo
    read Señal
    stty echo
    trap 2 3

Para terminar este asunto, abre un terminal gráfico y escribe en el prompt de comando lo siguiente:

$ trap "echo Cambió el tamaño de la ventana " 28

En seguida, coge el mouse (arghh!!) y arrástralo para variar el tamaño de la ventana actual. Sorprendido? Es el Shell orientado a eventos... smile

Uno mas, porque no me puedo resistir... Ahora escribe esto:

$ trap "echo acabó" 17

En seguida haz:

$ sleep 3 &

Acabas de crear un subshell que dormirá durante tres segundos en background. Al final de este tiempo, recibirás un mensaje acabó, porque la señal 17 es emitida cada vez que un subshell termina su ejecución.

Para volver estas señales a sus opciones por defecto, haz:

$ trap 17 28

O:

$ trap - 17 28

Acabamos de ver otras dos señales que no son tan importante como las que vimos anteriormente, pero voy a registrarlas en la tabla siguiente:

Señales no Muy Importantes
  28     SIGWINCH     Cambio de tamaño de la ventana gráfica  
Señal  Generada por:
  17     SIGCHLD     Fin de un proceso hijo  

Muy bueno este comando, verdad? Si tu descubres algun caso interesante del uso de señales, por favor informame por e-mail porque es muy rara la literatura sobre el asunto.

Comando getopts

El comando getopts recupera las opciones y sus argumentos de una lista de parámetros de acuerdo con la sintáxis POSIX.2, o sea, letras (o números) después de un señal de menos (-) seguidas o no de un argumento; en el caso de tener solamente letras (o números) se pueden agrupar. Debes usar este comando para "cortar en partes" opciones y argumentos pasados hacia tu script.

Sintáxis:

    getopts cadenadeopciones nombre

La cadenadeopciones debe explicitar una cadena de caracteres con todas las opciones reconocidas por el script, así si este reconoce las opciones -a -b y -c, cadenadeopciones debe ser abc. Si deseas que una opción sea seguida por un argumento, coloca dos puntos (:) después de la letra, como en a:bc. Ésto le dice al getopts que la opción -a tiene la forma:

    -a argumento

Normalmente uno o más espacios en blanco separan el parámetro de la opción; al mismo tiempo, getopts también manipula parámetros que vienen pegados a la opción como en:

    -aargumento

cadenadeopciones no puede contener el signo de interrogación (?).

El nombre constante en la línea de sintaxis anterior, define una variable que cada vez que el comando getopts sea ejecutado, recibirá la próxima opción de los parámetros de posición y la colocará en la variable nombre.

getopts coloca un signo de interrogación (?) en la variable definida en nombre si encuentra una opción no definida en cadenadeopciones o si no encuentra el argumento esperado para una determinada opción.

Como ya sabemos, cada opción pasada por una línea de comandos tiene un índice numérico, así, la primera opción estará contenida en $1, la segunda en $2, y así continúa. Cuando el getopts obtiene una opción, almacena el índice del próximo parámetro a ser procesado en la variable OPTIND.

Cuando una opción tiene un argumento asociado (indicado por : en cadenadeopciones), getopts almacena el argumento en la variable OPTARG. Si una opción no posee argumento o el argumento esperado no se encontró, la variable OPTARG será "matada" (unset).

El comando termina su ejecución cuando:

  • Encuentra un parámetro que no comienza por menos (-);
  • El parámetro especial -- marca el fin de las opciones;
  • Cuando encuentra un error (por ejemplo, una opción no reconocida).

El ejemplo siguiente es meramente didáctico, y sirve para mostrar, en un pequeño fragmento de código, el uso pleno del comando.

$ cat getoptst.sh #!/bin/sh

# Ejecute así: # # getoptst.sh -h -Pimpressora arch1 arch2 # # y note que las informaciones de todas las opciones son exhibidas # # La cadena 'P:h' dice que la opción -P es una opción compleja # y requiere de un argumento, y que h es una opción simple que no requiere # argumentos.

while getopts 'P:h' OPT_LETRA do echo "getopts hizo la variable OPT_LETRA igual a '$OPT_LETRA'" echo " OPTARG es '$OPTARG'" done used_up=`expr $OPTIND - 1` echo "Ignorando los primeros \$OPTIND-1 = $used_up argumentos" shift $used_up echo "Lo que sobró de la línea de comandos fue '$*'"

Para entenderlo mejor, vamos a ejecutarlo como está sugerido en su encabezado:

$ getoptst.sh -h -Pimpresora arch1 arch2 getopts hizo la variable OPT_LETRA igual a 'h' OPTARG es '' getopts hizo la variable OPT_LETRA igual a 'P' OPTARG es 'impresora' Ignorando los primeros $OPTIND-1 = 2 argumentos Lo que sobró de la línea de comandos fue 'arch1 arch2'

De esta forma, sin tener mucho trabajo, separé todas las opciones con sus respectivos argumentos, dejando solamente los parámetros que fueron pasados por el operador para un tratamiento posterior .

Fíjate que si hubiesemos escrito la línea de comando con el argumento (impresora) separado de la opción (-P), el resultado sería exactamente el mismo, excepto por el $OPTIND, ya que en este caso él identifica un conjunto de tres opciones/argumentos y en el anterior solamente dos. Mira esto:

$ getoptst.sh -h -P impresora arch1 arch2 getopts hizo la variable OPT_LETRA igual a 'h' OPTARG es '' getopts hizo la variable OPT_LETRA igual a 'P' OPTARG es 'impresora' Ignorando los primeros $OPTIND-1 = 3 argumentos Lo que sobró de la línea de comandos fue 'arch1 arch2'

En el ejemplo siguiente, fíjate que si pasamos una opción inválida, la variable $OPT_LETRA recibirá un signo de interrogación (?) y la $OPTARG será "matada" (unset).

$ getoptst.sh -f -Pimpresora arch1 arch2 # La opción ?f no es válida ./getoptst.sh: illegal option -- f getopts hizo la variable OPT_LETRA igual a '?' OPTARG es '' getopts hizo la variable OPT_LETRA igual a 'P' OPTARG es 'impresora' Ignorando los primeros $OPTIND-1 = 2 argumentos Lo que sobró de la línea de comandos fue 'arch1 arch2'

- Dime una cosa: no podrías haber usado un case para evitar el getopts?

- Podría si, pero para que? Los comandos están ahí para ser usados... El ejemplo dado fue didáctico, pero imagina un programa que aceptase muchas opciones y sus parámetros podrían no estar pegados a las opciones, sus opciones también podrían o no estar pegadas, iba a ser un case infernal y con=getopts= es sólo seguir los pasos que vimos anteriormente.

- Realmente... Viéndolo de esta forma, me parece que tienes razón. Sera porque ya estoy medio cansado con tanta información nueva en mi cabeza. Vamos a tomar la del estribo o todavia quieres explicar alguna particularidad mas del Shell?

- Ni lo uno ni lo otro, yo también me cansé, pero hoy no voy a tomar la del estribo porque estoy yendo a dar clases en la UniRIO, que es la primera universidad federal del Brasil que está preparando a sus alumnos del curso de graduación en informática, en el uso del Software Libre.

Pero antes te voy a dejar un problema para embarullar tu cabeza: quando tu varías el tamaño de una ventana gráfica, en el centro no aparece dinámicamente en vídeo inverso la cantidad de líneas y columnas? Entonces! Quiero que reproduzcas eso usando el lenguaje Shell.

- Mozo, traeme rapidito mi cuenta! Voy a contar hasta uno y si no me la trajiste me voy!

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é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 - 31 Jan 2007

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.