"¡No creerías cuantos administradores creen que puedes tener un bebé en un mes embarazando a nueve mujeres!"
-- Marc Wilson
Bueno, este debe ser el último artículo en la serie "Introducción a la Escritura de Guiones de Shell" - Tuve una buena reacción de parte de una cantidad de lectores (¡y gracias a todos por sus afables comentarios!), pero ya hemos cubierto la mayoría de lo básico de la escritura de guiones de shell; y ese ha sido el propósito original de la serie. Aún debo rever algún punto en el futuro ("¡Oh, cielos, olvidé explicar XYZ!"), pero aquellos de ustedes que nos han seguido a lo largo de la serie ahora se pueden considerar a sí mísmos Expertos, calificados para llevar un maletín y sonar importantes... <Hehe> Bueno, al final deben tener una buena idea de cómo escribir un script y hacerlo funcionar - y esa es una conveniente habilidad.
Bastante tiempo atrás, me encontré a mí mísmo en un dilema mientras escribía un guión (¡NO-O-O! ¡Cuan inusual! <Hehe>); tenía un array que contenía una lista de comandos que yo necesitaba ejecutar basándome en ciertas condiciones. Podía leer el array bastánte fácilmente, o imprimir cualquiera de las variables - pero lo que necesitaba era ¡ejecutarlo! ¿Que hacer? ¿Que hacer?... Como lo recuerdo, me rendí por falta de esa capacidad, y reescribí completamente el (bastante largo) guión (y no fué una experiencia agradable). "eval" pudo haber sido la solución.
He aquí como funciona - crear una variable llamada $cmd, como:
cmd='cat .bashrc|sort'
Es sólo un ejemplo - puedes usar cualquiér comando(s) válido(s).
Odin:~$ echo $cmd
cat .bashrc|sort
Odin:~$
- ¿pero como lo ejecutas? Sólo corriendo "cmd" produce un error:
Odin:~$ $cmd
cat: .bashrc|sort: No such file or
directory
Odin:~$
Aquí es donde "eval" viene a aparecer: eval $cmd" evalúa el contenido de una variable como si hubiese sido ingresada desde la línea de comandos. Esto no es algo que ocurra muy a menudo... pero es una capacidad del shell que necesitas tener en cuenta.
Nota que "bash" no tiene problemas ejecutando un comando simple que está almacenado como una variable, algo como:
Odin:~$ N="cat .bashrc" Odin:~$ $N # ~/.bashrc: executed by bash(1) for non-login shells. export PS1='\h:\w\$ ' umask 022funciona bien. Es sólo cuando comandos más complejos, por ej. en aquellos que involucran +alias u operadores ("|", ">", ">>", etc.) es donde encontrarás problemas - y para esas ocaciones, "eval" es la respuesta.
Una de las técnicas estandard en la escritura de guiones (y en programación en general) es la de escribir datos en ficheros temporales - hay muchas razones para hacer esto. Pero, y esta es una grande, ¿qué pasa cuando el usuario interrumpe ese guión a la mitad de la ejecución? (Para aquellos de ustedes que tienen guiones como ese y no lo han pensado, lamento darles material para pesadillas. Al menos espero mostrales también la solución.)
Ustedes pensaron esto: un desorden. Muchos ficheros en "/tmp", quizás importantes datos quedaron colgados de una briza (para ser borrados en el próximo reinicio), ficheros que pensaron actualizados pero no lo están...Ay! ¿Qué hay de una manera de salir airosos, a pesar de un frenético usuario golpea-teclados que tiene que correr "Quake" AHORA?
El comando "trap" provee una respuesta (al decir del usuario es mucho más efectiva y agradable, pero puede tenerte hablando de eso).
function cleanup ()
{
stty intr "" #
Ignora 'Ctrl-C'; déjale segir machacando...
echo "Despierta,
Neo."
sleep 2; clear
echo "Tu eres The Matrix."
echo "Helo aquí denuevo."|mail admin -s "Update stopped by $USER"
# Restaurar los datos originales
tar xvzf /mnt/backup/accts_recvbl
-C /usr/local/acct
# Borrar las cosas de 'tmp'
rm -rf /tmp/in_process/
# OK, nos hemos hecho cargo de la
limpieza. Ahora, es el tiempo de la VENGANZA!!!
rm /usr/games/[xs]quake
# Dale una contraseña nueva linda y
fácil-de-usar...
chpasswd $USER:~X%y!Z@zF%HG72F8b@Idiota&(~64sfgrnntQwvff########^
# Hemos respaldado
todas sus cosas... Oh, ¿que hace "--remove-files"?
tar cvz --remove-files
-f /mnt/timbuktu/bye-bye.tgz /home/$USER
# Heh-heh-heh...
umount /mnt/timbuktu
stty intr ^C # Vuelta a la normalidad
exit
# Ups, no quise hacer eso... Matar/colgar la shell.
}
trap 'cleanup' 2
...
Hay un poco de BOfH dentro de cada administrador. <sonrisa> (Para aquellos que no están familiarizados con "La Saga BOfH", esto es algo que debe leer cada administrador Unix; espantoso y odiosamente divertido. Busca en la Web.)
NO ejecutes este guión... si, se que es tentador. El punto de "trap" es, que podemos definir el comportamiento cuandoquiera que el usuario tipee `Ctrl-Break´ (o por si importa, cada vez que el script termina o es matado) y esto es mucho más útil que sólo salir del programa; esto te dá la oportunidad de ordenar, generar advertencias, etc.
"trap" también puede tomar otras señales; de hecho aún "kill", a pesar de su nombre, el mísmo no `mata´ un proceso - él le envía una señal. El proceso decide que hacer con la señal (una cruda descripción, pero generalmente correcta). Si deseas ver la lista entera de señales, solo tipea "trap -l" o "kill -l" o aún "killall -l" (lo que no lista los números de señales, solo los nombres). Las más comunmente usadas son: 1)SIGHUP, 2)SIGINT, 3)SIGQUIT, 9)SIGKILL, y 15)SIGTERM.
[Pero SIGKILL es inatrapable. -Ed.]
Existen también las señales `especiales´. Ellas son: 0)EXIT, que atrapa cualquier salida desde la shell, y DEBUG (sin número asignado), que puede - ¡he aquí una cosa ingeniosa! - ser usada como un script de shell reparador (el toma cada vez un comando simple ejecutado). DEBUG es realmente más que un ítem "solo informativo": puedes tener exactamente esta acción sin escribir ningún "traps", simplemente añadiendo "-x" a tu "hash-bang" (vea "EN CASO DE PROBLEMAS...").
"trap" es una poderosa herramientita. En LG·37, Jim Dennis da un breve fragmento de script que crea un directorio seguro bajo "/tmp" para esta clase de cosas - ficheros temporales que no quieres exponer al mundo. Un lindo artilugio; que usé varias veces.
Hablando de arreglaproblemas, "bash" provee varia y muy útiles herramientas que pueden ayudarte a encotrar errores en tu script. Estos son parámetros - parte del conjunto de la sintaxis del comando - que son usados en la línea de "hash-bang" del mísmo script. Estos parámetros son:
-n Lea la línea
del script de shell, pero no la ejecute
-v Imprima las
líneas como son leídas
-x Imprima
$PS4 (el indicador de "nivel de indirección") y el comando ejecutado.
Encuentro que "nv" y "-x" son las más útiles invocaciones: una te da la
exacta localización de una "mala" línea (puedes ver dónde el script puede
colgarse); la otra, `ruidosa´ como es, es útil por distinguir dónde las cosas
no están funcionando de la manera correcta (cuando, aunque la sintaxis es buena,
la acción no es la que querías). Buenas herramientas ambas. Según pasa el tiempo
y te acostumbras a la rareza de los reportes de error, probablemente las uses
menos y menos, pero son invaluables para el novato escritor de guiones.
Para usarlas, simplemente modifica el "hash-bang" inicial:
He aquí una línea familiar para cada programador "C":
#include <"stdio.h">
- un muy útil concepto, el de ficheros de fuente externos. Lo que esto significa es que un programador "C" puede escribir rutinas (funciones) que usará una y otra vez, almacenarlos en una `librería´ (un fichero externo), y traerlos cuando los necesite. Bueno - ¿no he dicho que el shell scripting es un lenguaje maduro y capáz? - nosotros podemos hacer lo mísmo! El fichero no siempre debe ser ejecutable; la sintaxis que usemos se ocupará de ello. El ejemplo debajo es un recorte de mi librería de función, "Funcky". Actualmente, es un sólo fichero, de un par de kb de extensión, y creciendo en espacio. Trato de mantener las funciones más útiles, porque no quiero llenar de basura el espacio (¿el concepto es aplicable a Linux? Debo descubrirlo....)
Hay un pequeño truco en el manejo de "bash" que vale la pena conocer: si creas una variable llamada BASH_ENV en tu .bash_profile, como:
export BASH_ENV="~/.bash_env"
y luego creas un fichero llamado ".bash_env" en tu directorio home, ese fichero será releido cada vez que inicies una shel ´no-login no-interactiva´ ej., un shell guión. Es un buen lugar para poner las cosas de inicialización es decir un guión de shell específico; es ahí donde yo tengo las fuentes de "Funky" - de esta manera, cualquier cambio en él está inmediatamente disponible para cualquier guión de shell.
cat /usr/local/bin/Funky|grep
\(\)
}
getch () #
toma un caracter del teclado, no "Enter" necesariamente
{
OLD_STTY=`stty
-g`
stty cbreak -echo
GETCH=`dd if=/dev/tty
bs=1 count=1 2>/dev/null`
stty $OLD_STTY
}
...
No tan diferente de un guión, no? No es necesario un "hash-bang", puesto que este fichero no se ejecuta a sí mísmo. Entonces, ¿cómo lo usamos en un guión? Helo aquí (pretenderemos que no fundamentamos "Funky" en "bash_env"):
. Funky
declare -i Total=0
leave ()
{
echo "¿Así que han
estado de compras?"
[ $Total -ne 0
] && echo "Dat'll be $Total bucks, pal."
echo "Que tengas
un buen dia."
exit
}
trap 'leave' 0
clear
while [ 1 ]
do
echo
echo "¿Que quieres
que? Tengo pepinos, tomates, lechuga, cebollas,"
echo "y radichetas hoy."
echo
# Esto es lo que
llamamos una función basada...
getch
# ...y referencia
a una variable creada por esa función.
case $GETCH
in
C|c) Total=$Total+1; ;;
T|t) Total=$Total+2; echo "Tomate maduro, ah?" ;;
L|l) Total=$Total+2; echo "Yo escogí la lechuga personalmente." ;;
O|o) Total=$Total+1; echo "Tan fresco que te hará llorar" ;;
R|r) Total=$Total+2; echo "Radichetas realmente crujientes." ;;
*) echo "No tengo náa como eso hoy, puesé mañá." ;;
esac
sleep 2
clear
done
Note el período después de "Funky": ese es un alias para el comando "fuente". Cuando es `fuenteado´,"Funky" adquiere una propiedad interesante: es como si hubiésemos pedido a "bash" ejecutar un fichero, el sale y revisa el camino listado en $PATH. Puesto que mantengo a "Funky"en "/usr/local/bin" (parte de mi $PATH), no necesito dar un camino explícito para el.
Si vas a escribir un guión de shel, te sugiero fuertemente que empiezes tu propia `libreria´ de funciones. (SUGERENCIA: Roba las funciones del ejemplo anterior!) Antes de tipearlos una y otra vez, un simple argumento "fuente" te conseguirá un montón de caramelos `empaquetados´.
Bueno - después de todo, un montón de tópicos han sido cubiertos,
algunas "rarezas" explicadas; todas buenas cosas, información útil para
el escritor de guiones. Hay un montón más de eso - recuerda, esta serie
fué sólo una introducción a la escritura de guiones de shel - pero
cualquiera que se me haya pegado desde el princípio y preserve en adelante
mi marca de lógica retorcida (¡pobres compañeros! irremediablemente dañados,
ni siquiera el mejor sicólogo del mundo podrá ayudarlos ahora....:) son capaces
ahora de diseñar, escribir, y reparar un guión de shell medianamente decente. El
resto - entendimiento y escritura de guiones más complejos, más complicados - sólo
puede venir con la práctica, conocido de otra manera como "cometer un montón
de errores". En ese espíritu, les deseo gran cantidad de "errores"!
Feliz Linuxeo!
Las comunidades lucharán por defenderse
a sí mísmas. La gente luchará más duro y más reñido por defender sus
comunidades, tanto como ellos lucharán por defender sus propias personalidasdes
individuales.''
-- Bruce Sterling, "Hacker Crackdown"
Las páginas "man" para 'bash', 'builtins', 'stty'
"Introducción a la Escritura de Guiones de Shell - Las Bases", LG #53
"Introducción a la Escritura de Guiones de Shell", LG #54
"Introducción a la Escritura de Guiones de Shell", LG #55
"Introducción a la Escritura de Guiones de Shell", LG #57