Los shell scripts son programas escritos con comandos UNIX y son equivalentes a los batch de DOS aunque mucho más potentes, pues admiten ejecuciones en segundo plano y tienen un conjunto de expresiones mucho más amplio.
Para realizar una shell script en condiciones, al igual que para realizar un programa en cualquier lenguaje de programación, es necesario tener claro lo que se quiere obtener y de que situación se parte. Es mejor perder un poco de tiempo en realizar un desarrollo mental del programa que dedicarse a teclear y probar cosas, pues lo más seguro es que no se consiga el objetivo, y si se consigue la estructura del programa no será la adecuada.
Una de las ventajas que presentan los shell scripts es que pueden ser portadas de una máquina UNIX a otra sin problemas, sin necesidad de retocar nada, salvo que se utilicen llamadas a programas muy concretos específicos de una versión de UNIX, mientras que los programas compilados (desarrollados en C, Pascal, etc.) deben ser recompilados, pues el código se generará en función del microprocesador de cada máquina. Otra ventaja es la facilidad de lectura e interpretación.
El principal inconveniente que presentan respecto a los programas compilados es la lentitud de ejecución, que se puede paliar usando las utilidades built-in incluidas en el propio kernel en lugar de llamar a comandos externos que han de ser leidos de disco. Otro inconveniente es que el código resulta visible a cualquier usuario que lo pueda ejecutar.
Se deben añadir comentarios con el fin de facilitar la lectura del programa. Los comentarios se insertan anteponiendo el carácter “#” al comentario, que se extenderá hasta el final de la línea.
Los scripts suelen encabezarse con comentarios que indican el nombre de archivo y lo que hace el script. Se colocan comentarios de documentación en diferentes partes del script para mejorar la comprensión y facilitar el mantenimiento. Un caso especial es el uso de “#” en la primera línea, seguido del carácter admiración y el path de la subshell, para indicar el intérprete con que se ejecutará el script, como por ejemplo:
- #!/bin/ksh
- #!/bin/sh
- #!/bin/bash
Es interesante saber que muchos comandos devuelven un valor después de ejecutarse, y que este valor indicará si la ejecución ha sido buena o si ha habido algún fallo y que tipo de fallo se ha producido. Para conocer si un comando devuelve o no un valor y qué es lo que devuelve en cada caso se deberá consultar la documentación, pero por lo general en caso de una ejecución correcta devolverán el valor 0, y en caso de fallo otro numero, positivo o negativo.
Para poder ejecutar un archivo de comandos es necesario que tenga activados, al menos, los permisos de lectura y ejecución. A continuación vamos a ver unos ejemplos, comenzando por cosas muy sencillas.
Ejemplo1:
# Programa que cambia de directorio y nos muestra donde estamos en cada
# momento. Cambia directorio en un subshell, no altera ambiente original
#
echo SUBSHELL.
DIRACT=`pwd`
echo Directorio actual $DIRACT
echo Cambiando directorio en el subshell…
cd /etc
echo Ahora en directorio `pwd`
cd
echo Ahora estoy en mi directorio, que es `pwd`
# fin subsh.cmd
El programa mostraría lo siguiente:
SUBSHELL.
Directorio actual /home/dvarela
Cambiando directorio en el subshell…
Ahora en directorio /etc
Ahora estoy en mi directorio, que es /home/dvarela
Ejemplo 2:
# Programa que muestra la hora del sistema cada segundo durante 1 minuto
Cont=0
while [ $Cont -le 60 ]
do
date
((Cont=$Cont + 1))
sleep 1
done
Ejemplo 3:
# Programa que nos dice el dia de la semana que fue ayer
HOY=$(date +%w) #En la variable HOY almacenamos el numero de dia para hoy.
AYER=$(expr $HOY – 1) # y en ayer, el valor de HOY menos 1
echo “Ayer fue \c”
case $AYER in
-1) echo “Sabado”;;
0) echo “Domingo”;; # date +%w devuelve el día de la semana
1) echo “Lunes”;; # en formato numerico, con valores
2) echo “Martes”;; # comprendidos entre 0 (domingo) y 6
3) echo “Miercoles”;; # (sabado). En nuestro caso, ayer tomara
4) echo “Jueves”;; # valores entre -1 y 5.
5) echo “Viernes”;;
*) echo “un dia raro, pues no existe”;;
esac
Ejemplo 4:
#!/bin/ksh
# Programa que pide al usuario que introduzca una cadena de caracteres y
# la muestra, por pantalla del derecho y del revés.
#
echo “Introduce una cadena: \c”
read NOMBRE
LONGITUD=${#NOMBRE}
while [ $LONGITUD -gt 0 ]
do
NOMBREALREVES=”$NOMBREALREVES”`echo $NOMBRE | cut -c$LONGITUD`
LONGITUD=`expr $LONGITUD – 1` # ((LONGITUD=LONGITUD+1)) seria mejor
done
echo “\n$NOMBRE\n$NOMBREALREVES”
El programa mostraría en pantalla:
Introduce una cadena: Hola que tal?
Hola que tal?
¿lat euq aloH
Ejemplo 5:
#!/bin/ksh
# Programa que simula una papelera de reciclaje, mediante el cual en lugar de
# borrar un archivo, lo que hace es guardarlo en un subdirectorio, con el fin
# de evitar borrados accidentales.
#
if [ $# -gt 0 ]
then
for i in $*
do
echo “Moviendo $i al directorio $HOME/borrados”
if [ `mv $i $HOME/borrados 2> /dev/null` ¡= 0 ]
then
echo “Error, no se puede mover $i”
fi
done
else
echo “Error: hay que especificar argumentos”
echo “$0 archivo1 [archivo2] …”
return 1
fi
return 0
Ejemplo 6:
# Programa que ejecuta un proceso largo, y mientras tanto va mostrando un punto en la pantalla,
# simulando que el proceso va progresando.
function puntos ()
{
if [ $1 ] # Si se ha pasado algun argumento
then
INTERVALO=$1 # Considerar el intervalo como tal
else
INTERVALO=5 # Si no se ha pasado, considerar 5
fi
while true # Ejecutar siempre, sin fin.
do
echo “.\c” # Imprimir un punto si salto de linea
sleep $INTERVALO # Esperar el intervalo especificado
done
}
# Programa principal
# Lo que sea
puntos 2 & # Se llama a la funcion puntos, con intervalos de 2 sg
[ programa que tarda mucho ]
kill -9 $! # Se mata el ultimo proceso lanzado en background
# Lo que sea
# Fin del programa
Ejemplo 7:
# Programa encargado de comprobar la hora de los diferentes sistemas conectados
# a un sistema determinado
# En la variable SISTEMAS_TOT se definen los sistemas de los que se intentara
# obtener la hora.
SISTEMAS_TOT=”maquina1 maquina2 maquina3 maquina4 maquina5 maquina6 maquina7″
# La funcion hora se encarga de pedir la hora al sistema especificado,
# filtrarla y mostrarla por pantalla.
hora()
{
hora=´telnet $1 daytime 2> /dev/null | grep “:” ´
echo “$hora —–> $1”
}
# Comprobar si el sistema esta accesible, y si lo esta, anadirlo a la variable
# SISTEMAS, que sera la que contiene los sistemas accesibles.
for SISTEMA in $SISTEMAS_TOT
do
/usr/sbin/ping $SISTEMA -n 1 | grep ” 0% packet loss” > /dev/null
if [ $? = 0 ]
then
SISTEMAS=”$SISTEMAS $SISTEMA”
else
echo “$SISTEMA no esta accesible”
fi
done
#Para los sistemas accesibles comprobar la hora de los mismos en background
#para minimizar diferencias.
for SISTEMA in $SISTEMAS
do
hora $SISTEMA &
done
#Esperar a que finalice la ejecucion de todas las tareas en background.
wait
#Fin de programa
Procesamiento de Opciones (getopts)
Normalmente las opciones son precedidas por los signos – (activa la opción) o + (la desactiva). Las opciones así marcadas pueden ser tratadas mediante getopts.
Sintaxis:
getopts [:]opciones [argumentos] variables
Donde:
* : Se emplea para el tratamiento de errores, establece el valor de variables a ? y establece el valor de la variable de entorno OPTARG al nombre de la opción inválida.
* opciones son los caracteres válidos que espera el script como opciones desde la línea de comandos.
* argumentos son los argumentos (opcionales) que pueden requerir las opciones
* variables es el nombre de cuyo valor es una opción de la línea de comandos si la opción es uno de los caracteres válidos.
Ejemplos:
while getopts abc opcion
do
case $opcion in
a) print “Opcion -a”;;
b) print “Opcion -b”;;
c) print “Opcion -c”;;
+a) print “Opcion +a”;;
+b) print “Opcion +b”;;
+c) print “Opcion +c”;;
esac
done
#funcionara con llamadas como
# script -a -b +c
# script -ac +b
#
USAGE=”Sintaxis: $0 -a -b
while getopts :ab opcion
do
case $opcion in
a) print “Opcion -a”;;
b) print “Opcion -b”;;
+a) print “Opcion +a”;;
+b) print “Opcion +b”;;
\?) echo “$OPTARG no es una opcion valida.\n $USAGE”;;
esac
done
Cuando una opción debe ir seguida de un argumento se indica añadiendo : después de la opción, con lo que OPTARG tomará el valor del argumento. También se añade una opción : al case para tratar la falta de argumento.
Ejemplos:
USAGE=”Sintaxis: $0 -ac [-b argumento]
while getopts :ab:c opcion
do
case $opcion in
a) print “Opcion -a”;;
b) programa $OPTARG;; # La opcion -b debe llevar un argumento
c) print “Opcion -c”;;
\?) echo “$OPTARG no es una opcion valida.\n $USAGE”;;
🙂 echo “La opcion $OPTARG requiere un argumeno.\n $USAGE”;;
esac
done
¿Como emular el DOSKEY en UNIX (Esc-K)?
En el archivo $HOME/.profile deberá exitir, si no existe habrá que añadir en un punto por el que siempre se pase -generalmente el principio- lo siguiente:
HISTFILE=$HOME/.history
HISTSIZE=64
EDITOR=/usr/bin/vi # el PATH correcto donde se encuentre ubicado el comando vi
export HISTFILE HISTSIZE EDITOR
Antes de usar $HOME, por supuesto que debe estar definido. El tamaño 64 es recomendado, puedes poner el que quieras. El nombre del archivo en principio puede ser cualquiera, pero eligelo con lógica.
Si trabajas bajo una plataforma con entorno gráficos, es posible que no fucione, para ello deberás localizar el .profile equivalente, o habilitar algún parámetro definido en dicho profile, que permita correr el .profile. Bajo la plataforma HP-UX, en el archivo .dtprofile, bajo tu directorio, hay que activar el parámetro DTSOURCEPROFILE=TRUE (quitale el comentario (#)). Por supuesto debes reiniciar la sesión en cualquiera de los casos.
Una Papelera de Reciclaje. No borres tus archivos de forma definitiva. Recuperalos!!!
( ¡ No es aplicable a root por motivos de seguridad en sistemas multiusuario ! )
Crea un directorio (en tu directorio de usuario) con un nombre cualquiera, pero significativo (p.e. trash). En tu $HOME o en el directorio en que tengas tus ejecutables, create un archivo llamado rm, e inserta en él:
/bin/mv $* $HOME/trash # /bin se debe sustituir por la ubicación real de mv
Deberá tener permiso, al menos, de ejecución y lectura para el propietario:
chmod 700 rm
En tu archivo .profile deberás cambiar PATH=…, de modo que aparezca antes el directorio que contiene el nuevo rm que el directorio que contiene el rm real (/bin o /usr/bin, depende del sistema), con el fin de que sea localizado antes el nuevo rm que el original.
Con esto lo que se consigue es mover los archivos que se quieren borrar a un subdirectorio, por si interesara recuperarlos. Deberás limpiar este directorio de forma periódica, para lo cual te puedes generar un archivo en el mismo directorio que el anterior que contenga una instrucción como:
/bin/rm $HOME/trash/*
o en el .profile:
find $HOME/trash -atime [DIAS] -exec /bin/rm {} \;
Backgrounds activos
Si ejecutas un shell script que tarda algún tiempo, lo suficiente como para mosquear al usuario, y quieres que el usuario se crea que todo va bien, prueba a incluir lo siguiente en tu shell script (puedes cambiar el punto que se imprime por cualquier carácter o símbolo, pero cuidado con las secuencias de escape):
function puntos ()
{
if [ $1 ]
then
tiempo = $1 # Lo que le indiques
else
tiempo = 5 # 5 segundos por defecto
fi
while true
do
echo “.\c”
sleep $tiempo
done
}
Programa principal:
puntos 1 & # Cada segundo imprimirá un punto
PROGRAMA # programa a ejecutar
kill -9 $! # Finaliza el último programa lanzado en background presenta los resultados como quieras
if [ -f $out ] # Si se generó el archivo de salida. Prueba a insertar otras funciones
then
echo “Los resultados estan en $SALIDA”
else
echo “Algo ha fallado”
fi
¿Aliases o Funciones?
Habitualmente los aliases se guardan en un archivo cuyo nombre suele ser .aliases, o .env, se puede comprobar visualizando la variable de entorno $ENV. En otras ocasiones puede estar almacenado en el propio .profile. Los aliases vienen a tener la siguiente estructura:
alias ll=”ls -l” o alias ll=´ls -l´
alias fcore=”find / -name core -print -exec rm{} \;”
pero podemos comprobar que no admiten variables como argumentos. Para solucionar esta carencia lo que podemos usar son funciones, teniendo en cuenta que $1, $2, etc. Son los argumentos que se pasan a la función. Evidentemente, al tratarse de funciones, las podemos construir tan complejas como deseemos, pudiendo llegar a ser auténticos programas.
Como ejemplo veamos la siguiente función que busca una cadena de caracteres en todos los archivos que cuelgan de un subdirectorio determinado, y en los subdirectorios que a su vez cuelgan de él, es decir, en el árbol descendente del subdirectorio que elijamos.
function Ffind ()
{
if [ $2 ] # Se comprueba si existe 1 o 2 argumentos
then
DIR_INICIAL=$1
CADENA=$2
else
if [ $1 ]
DIR_INICIAL=.
CADENA=$1
else
echo “Sintaxix: Ffind <directorio> <cadena>”
return 0
fi
fi
# Ahora obtenemos el árbol de directorios sobre el que hay que buscar
DIRECTORIOS=´find $DIR_INICIAL -depth -type d -print´
for i in $DIRECTORIOS
do
echo “Buscando $CADENA en $i”
grep $CADENA $i/* # Puedes usar las opciones que quieras del comando grep
done
}
Esta función estaría en el archivo de aliases y sería de uso exclusivo del propietario, invisible a los demás.
La puedes mejorar con más opciones, emplear recursividad, etc., el caso es que debe ser útil y lo más rápida posible, para lo cual debe consumir el mínimo de recursos. La sintaxis que se emplearía, en el caso expuesto, sería:
Ffind <directorio> <cadena>
Un ejemplo de alias dinámico, que cambie el directorio de acceso en función del año en que nos encontremos puede ser del tipo:
alias cdano=”ANO=`date +%Y` ; cd /home/user/$ANO
El resto es cuestión de imaginación.
Borrar archivos que contienen metacaracteres en su nombre.
En ocasiones se generan archivos, por error o no, que continen caracteres extraños o metacaracteres como ?, *,^C ,^H, etc. y que no pueden ser borrados habitualmente con el comando rm. Para eliminar estos archivos se puede emplear el siguiente método:
1. Listar el directorio en el que se encuentre con el comando ls -li, con el fin de anotar el i-nodo que le corresponde.
ls -li [directorio]
2. Buscar en ese directorio el archivo con el número de i-nodo que le corresponde y borrarlo:
find [directorio] -inum [i-nodo] -ok -exec rm {} \;
La opción -ok se incluye para que pida confirmación.
Ejecución de comandos en otra máquina sin tener que acceder explícitamente.
Para ello puedes realizar un rsh (remote shell, no restricted shell) o remsh indicando el nombre de la máquina, pero si generas un archivo con el nombre de la máquina en tu directorio de ejecutables, y lo linkas al rsh o remsh podras realizar la misma operación tecleando simplemente el nombre de la máquina y el comando a ejecutar. Esto viene documentado, solo tienes que hacer un man rsh (o remsh, dependiendo del UNIX que tengas cargado) y leer por el medio.
Por supuesto debes tener tener cuenta en dicha máquina y el acceso habilitado mediante el archivo .rhosts bajo tu directorio.
Visualizar los procesos ejecutados por todos los usuarios conectados al sistema (whodo).
Este script, que simula el comando whodo, permite visualizar los programas que tiene cada usuario en ejecución, así como su dirección IP, tiempo de inactividad, terminales asociados, etc.
#!/usr/bin/ksh
usuarios=`who -u`
usu=`echo “$usuarios” | cut -f1 -d ” ” | sort -u`
for i in $usu
do
echo “$usuarios” |grep $i
ps -x -u $i
done