- Comandos trap/exec/exit.
Como se vió en un capítulo anterior , cualquier proceso UNIX es susceptible de recibir señales a lo largo de su tiempo de ejecución. Por ejemplo , si el sistema se apaga (shutdown) , todos los procesos recibe n de entrada una señal 15 (SIGTERM) , y , al rato , una señal 9 .Por ahora , recordaremos que sólamente la señal 9 (SIGKILL) no puede ser ignorada ni redirigida . Un programa puede cambiar el tratamiento que los procesos hacen respecto de las señales , que suele ser terminar.
Las shell-scripts pueden efectuar cosas dependiendo de la señal que reciban , usando el comando "trap <comandos> <señales>" . Este comando se suele poner al principio de la shell para que siga vigente a lo largo de toda la ejecución.
Así , podemos directamente ignorar una señal escribiendo lo siguiente :
trap "" 15
(Si llega la señal 15 (SIGTERM) , no hagas nada)
O evitar que nos pulsen Control-C , y si lo hacen , se acabó :
trap ‘echo "Hasta luego lucas" ; exit 1’ 2 3 15
(Si llega cualquiera , sacar el mensajito y terminar la ejecución con código de retorno 1).
Ojo con la ejecución de subshells - las señales ignoradas (las del trap ‘’) se heredan pero las demás vuelven a su acción original.
El comando ‘exec <comando>’ , aparte de una serie de lindezas sobre redireccionamientos cuyo ámbito queda fuera de éste manual , reemplaza el proceso de la shell en cuestión con el del comando , el cual debe s er un programa ó otra shell-script. Las implicaciones que ésto tiene son que no se vuelve de dicha ejecución. Veamoslo , para no perder la costumbre , con un ejemplo :
Caso 1:
echo "Ejecuto el comando ls"
ls
echo "Estamos de vuelta"
Caso 2:
echo "Ejecuto el comando ls"
exec ls
echo "Estamos de vuelta"
En el caso -1- , la shell actual ejecuta un hijo que es el comando "ls", espera a que termine y vuelve , es decir , sigue ejecutando el echo "Estamos de vuelta". Sin embargo , en el caso -2- ésto no es as&iacu te; ; hacer exec implica que el número de proceso de nuestra shell pasa a ser el del comando "ls" , con lo que no hay regreso posible , y por tanto , el echo "estamos de vuelta" , NUNCA podría ejecutarse.
Por tanto , al ejecutar un nuevo programa desde exec , el número de proceso (PID) no varía al pasar de un proceso a otro.
-Comando exit <código de retorno>.
Como ya hemos visto parcialmente , éste comando efectúa dos acciones:
Termina de inmediato la ejecución del shell-script , regresando al programa padre (que lógicamente podría ser otra shell ó directamente el inductor de comandos).
Devuelve al proceso antes citado un código de retorno , que podremos averiguar mirando la variable -$?- .
Al terminar una shell script , el proceso inmediatamente antes de acabar , recibe una señal 0 , útil en ocasiones para ver por dónde hemos salido usando el comando "trap".
- Funciones.
De manera similar a las utilizadas en lenguajes convencionales , dentro de una shell se pueden especificar funciones y pasarle parámetros. La sintaxis de una función sería :
nombre_funcion()
{
... comandos ...
}
Las variables de entorno de la función son las mismas que las de la propia shell-script ; debemos imaginar que la propia función se halla "incluída" en la porción de código desde donde la inv ocamos . Por tanto , una variable definida en una función queda definida en la shell y viceversa .
La excepción se produce con los parámetros posiciones ; el $1 , $2 .... cambian su sentido dentro de las funciones , en las cuales representan los parámetros con los que se la ha llamado.
Veamos para variar un ejemplo :
# programa de prueba - llamar con parametros
echo "Hola $1"
pinta()
{
echo "Hola en la función $1"
}
pinta "gorgorito"
Veamos el resultado de la ejecución :
# sh prueba "probando"
Hola probando
Hola en la funcion gorgorito
La variable -$1- , dentro del programa toma el valor del primer parámetro (probando) , y dentro de la función , el parámetro que se le ha pasado a la función (gorgorito). Una vez la función termina , e l $1 vale lo mismo que al principio.
La función nos puede devolver códigos de retorno utilizando la cláusula "return <codigo de error>".
- Ejecución en segundo plano : & , wait y nohup.
Si un comando en la shell termina en un umpresand -&- , la shell ejecuta el comando de manera asíncrona , es decir , no espera a que el comando termine .
La sintaxis para ejecutar un comando en segundo plano es :
comando &
Y la shell muestra un numerito por la pantalla , indicativo del número del proceso que se la lanzado. Hay que tener en cuenta que ése proceso es hijo del grupo de procesos actual asociado a nuestro terminal ; significa que si apagamos el terminal ó terminamos la sesión , tal proceso se cortará.
Para esperar a los procesos en segundo plano , empleamos el comando "wait" que hace que la shell espere a que todos sus procesos secundarios terminen.
Siguiendo con lo de antes , hay veces que ejecutamos :
# comando_lentisimo_y_pesadisimo &
Y queremos apagar el terminal e irnos a casa a dormir ; a tal efecto , existe el comando "nohup" (traducción : no cuelgues , es decir , aunque hagas exit ó apagues el terminal , sigue) , que independiza el proces o en segundo plano del grupo de procesos actual con terminal asociado.
Lo que ésto último quiere decir es que UNIX se encarga de ejecutar en otro plano el comando y nosotros quedamos libres de hacer lo que queramos.
Una duda : al ejecutar un comando en background , la salida del programa nos sigue apareciendo en nuestra pantalla , pero si el comando nohup lo independiza , que pasa con la salida ? La respuesta es que dicho comando crea un fichero ll amado "nohup.out" en el directorio desde donde se ha lanzado , que coniene toda la salida , tanto normal como de error del comando.
Ejemplo sobre cómo lanzar comando_lentisimo_y_pesadisimo :
# nohup comando_lentisimo_y_pesadisimo &
Sending output to nohup.out
12345
#
El PID es 12345 , y la salida del comando la tendremos en el fichero "nohup.out" , el cual es acumlativo ; si lanzamos dos veces el nohup , tendremos dos salidas en el fichero.
- Comandos a ejecutar en diferido : at , batch y cron.
Estos tres comandos ejecutan comandos en diferido con las siguientes diferencias :
AT lanza comandos una sola vez a una determinada hora.
BATCH lanza comandos una sola vez en el momento de llamarlo.
CRON lanza comandos varias veces a unas determinadas horas , días ó meses.
Estos comandos conviene tenerlos muy en cuenta fundamentalmente cuando es necesario ejecutar regularmente tareas de administración ó de operación. Ejemplos de situaciones donde éstos comandos nos pueden ayuda r son :
- Necesitamos hacer una salva en cinta de determinados ficheros todos los días a las diez de la mañana y los viernes una total a las 10 de la noche = CRON.
- Necesitamos dejar rodando hoy una reconstrucción de ficheros y apagar la máquina cuando termine ( sobre las 3 de la mañana ) , pero nos queremos ir a casa (son ya las 8) =AT
- Necesitamos lanzar una cadena de actualización , pero estan todos los usuarios sacando listados a la vez y la máquina está tumbada = BATCH
-Comando at <cuando> <comando a ejecutar>
Ejecuta , a la hora determinada , el <comando>. Puede ser una shell-script , ó un ejecutable.
Este comando admite la entrada desde un fichero ó bien desde el teclado. Normalmente , le daremos la entrada usando el "here document" de la shell.
El "cuando" admite formas complejas de tiempo. En vez de contar todas , veamos algunos ejemplos que seguro que alguno nos cuadrará :
* Ejecutar la shell "salu2.sh" que felicita a todos los usuarios , pasado mañana a las 4 de la tarde:
# at 4pm + 2 days <<EOF
/usr/yo/salu2.sh
EOF
* Lanzar ahora mismo un listado:
at now + 1 minute <<EOF
"lp -dlaserjet /tmp/balance.txt"
EOF
* Ejecutar una cadena de reindexado de ficheros larguísima y apagar la máquina:
at now + 3 hours <<EOF
"/usr/cadenas/reind.sh 1>/trazas 2>&1 ; shutdown -h now"
EOF
* A las 10 de la mañana del 28 de Diciembre mandar un saludo a todos los usuarios :
at 10am Dec 28 <<EOF
wall "Detectado un virus en este ordenador"
EOF
* A la una de la tarde mañana hacer una salvita :
at 1pm tomorrow <<EOF
/Tutoriales/Salvas/diario.sh
EOF
De la misma manera que el comando nohup , éstos comandos de ejecución en diferido mandan su salida al mail , por lo que hay que ser cuidadoso y redirigir su salida a ficheros personales de traza en evitación de satu rar el directorio /var/mail.
El comando "at" asigna un nombre a cada trabajo encolado , el cual lo podemos usar con opciones para consultar y borrar trabajos :
at -l : lista todos los trabajos en cola , hora y día de lanzamiento de los mismos y usuario.
at -d <trabajo>: borra <trabajo> de la cola.
Ya que el uso indiscriminado de éste comando es potencialmente peligroso , existen dos ficheros que son inspeccionados por el mismo para limitarlo : at.allow y at.deny. (Normalmente residen en /usr/spool/atjobs ó en /var/a t).
at.allow:si existe, solo los usuarios que esten aquí pueden ejecutar el comando "at".
at.deny:si existe, los usuarios que estén aquí no estan autorizados a ejecutar "at".
El "truco" que hay si se desea que todos puedan usar at sin tener que escribirlos en el fichero , es borrar at.allow y dejar sólo at.deny pero vacío.
- Batch .
Ejecuta el comando como en "at" , pero no se le asigna hora ; batch lo ejecutará cuando detecte que el sistema se halla lo suficientemente libre de tareas. En caso que no sea así , se esperará hasta que oc urra tal cosa.
-Cron.
No es un comando ; es un "demonio" , ó proceso que se arranca al encender la máquina y está permanentemente activo. Su misión es inspeccionar cada minuto los ficheros crontabs de los usuarios y ejec utar los comandos que allí se digan a los intervalos horarios que hayamos marcado. Como se acaba se señalar , los crontabs son dependientes de cada usuario.
Se deben seguir los siguientes pasos para modificar , añadir ó borrar un fichero crontab :
1 - Sacarlo a fichero usando el comando "crontab -l >/tmp/mio" , por ejemplo.
2 - Modificarlo con un editor de acuerdo a las instrucciones de formato que se explican a continuación.
3 - Registrar el nuevo fichero mediante el comando "crontab /tmp/mio" , por ejemplo.
mira cada minuto el directorio /var/spool/crontabs y ejecuta los comandos
crontab .
El formato del fichero es el siguiente :
#minutos horas dia mes mes dia-semana comando ( # = comentario )
minutos : de 0 a 59.
horas : de 0 a 23.
dia del mes : de 0 a 31.
mes : de 0 a 12.
día semana : de 0 a 6 ( 0 = domingo , 1 = lunes ...)
Aparte de la especificación normal , pueden utilizarse listas , es decir , rangos de valores de acuerdo con las siguientes reglas :
8-11 : Rango desde las 8 hasta las 11 ambas inclusive.
8,9,10,11 : Igual que antes ; desde las 8 hasta las 11.
Si queremos incrementar en saltos diferentes de la unidad , podemos escribir la barra "/" :
0-8/2 : Desde las 12 hasta las 8 cada 2 horas. Equivale a 0,2,4,6,8 .
El asterisco significa "todas" ó "todo". Por tanto :
*/2 : Cada dos horas.
Ejemplos más evidentes :
# ejecutar una salvita todos los dias 5 minutos despues de las 12 de la noche :
5 0 * * * /Tutoriales/salvas/diaria.sh 1>>/Tutoriales/salvas/log 2>&1
# ejecutar a las 2:15pm el primer dia de cada mes:
15 14 1 * * /Tutoriales/salvas/mensual.sh 1>/dev/null 2>&1
# ejecutar de lunes a viernes a las diez de la noche - apagar la maquina que es muy tarde
0 22 * * 1-5 shutdown -h now 1>/dev/null 2>&1
# ejecutar algo cada minuto
* * * * * /Tutoriales/cosas/espia.sh
4 TRATAMIENTO DE FICHEROS.
Comando :wc <opciones> <nombre-archivo>
Misión: cuenta lineas,palabras o caracteres de un fichero.
opciones:
-l:lineas
-c:letras
-w:palabras
Ejemplo:
l=`wc -l /etc/passwd` ; echo "Hay $l usuarios registrados en este equipo"
En la variable $l tendríamos el número de líneas del fichero /etc/passwd.
Comando: tr <opciones> 'caracteres orig' 'caracteres destino' < nombre-archivo
Misión: sustituye globalmente un caracter por otro .
Ejemplo:
tr '[a-z]' '[A-Z]' < /etc/passwd >/tmp/passwd.MAYUSCULAS
Pasa el fichero de entrada a mayúsculas.
Comando: cut <opciones> <nombre de archivo>
Misión: corta texto a través del número absoluto de columna o el número relativo de
campo.
Opciones:
-c:numero absoluto de columna
-f:numero de campo.
-d:delimitador de campo.
Especificación de números de columna.
1,3,10 : primera , tercera y decima columnas.
1-3 : de la primera a la tercera columna.
1-3,8 : de la primera a la tercera y la octava.
-5 : de la primera a la quinta columna
5- : de la quinta a la última.
Ejemplo :
cut -d"." -f1 /etc/passwd
lista sólamente los nombres del fichero /etc/passwd , cortando por el delimitador ‘:’ .
Comando: paste.
Misión:
Comando : split <-numero de lineas de los ficheros> <fichero>
Misión: divide <fichero> en n ficheros de <num.lin> cada uno. Si no se indica el
numero de lineas , se consideran 1000.
los ficheros que genera se llaman xaa , xab , xac ...
Ejemplo:
$ split gordo
$ ls
gordo xaa xab xac xad xae xaf xag
Ha partido el fichero "gordo" en ficheros más pequeños , de 1000 líneas cada uno , denominados "xaa" ,"xab" ..... Si borramos el fichero "gordo" , podríamos volver a ge nerarlo con el comando siguiente:
$ cat xaa xab xac xae xaf xag > gordo
Comando: sort <opciones> <indicador de campo> <fichero>
Misión: ordena un fichero de acuerdo a una secuencia de odenacion determinada.
Ejemplo
dado un fichero con:
codigo nombre ciudad telefono
$ sort fichero # ordena por todos los campos
$ sort +1 fichero # ordena a partir del campo 1 (nombre ; codigo es el 0)
$ sort +2n fichero # ordena por nombre , en secuencia invertida.
$ sort +2n -t":" fichero # igual que antes , pero suponiendo que el separador es ":".
Comando: grep <cadena a buscar> <fichero>
Misión: buscar apariciones de palabras ó textos en ficheros.
Ejemplo:
$ grep root /etc/passwd # busca las líneas que contienen "root" en el fichero.
root:Ajiou42s:0:1:/:/bin/sh
Otro:
$ ps -e | grep -v constty # busca qué procesos no ruedan en la consola
( ...... )
La familia de grep , así como otros comandos tales como de (editor de líneas), sed (editor batch) y vi , manejan expresiones regulares para la búsqueda de secuencias de caracteres. Una expresión regular es un a expresión que especifica una secuencia de caracteres. Un ejemplo de ésto es el comando : ls -l | grep "^d" ; el "^d" es una expresión regular que equivale a decir "si la letra -d- está al principio de la línea" ; la construcción anterior nos lista sólo los directorios.
Las expresiones regulares conviene esconderlas de la shell encerrándolas entre comillas.
Algunos montajes de expresiones regulares son :
<.> (Punto) :cualquier carácter distinto al del fin de línea.
[abc] :la letra a , la b ó la c.
[^abc] :cualquier letra distinta a a , b ó c.
[a-z] :cualquier letra de la -a- a la -z- .
Una letra seguida de un asterisco -*- equivale a cero ó más apariciones de la letra. Por tanto , la expresión .* equivale a decir "cualquier cosa". Por tanto, el comando :
ls -l | grep ‘\.sh.*"
lista todos los ficheros que terminen en .sh mas los terminados en .sh<otras letras> . Nótese que el punto inicial lo hemos desprovisto de su significado "escapándolo" con un backslash -\- .
^ : al principio de una expresión regular equivale a "desde el principio de la línea".
$ : al final de una expresión regular equivale a "hasta el final de la línea".
Ejemplos de ésto :
ls -l | grep ‘\.sh$’ : saca los ficheros que SOLO acaben en .sh
Este tema se complica todo lo que se quiera . Veamos algunas expresiones regulares bastante usadas en comandos tipo sed ( que veremos más tarde ) para percatarse de ello :
[^ ] : cualquier letra diferente de espacio.
[^ ]* : una palabra cualquiera (varias letras no blancas).
[^ ]* * : una palabra seguida de un número incierto de blancos.
^[^ ]* * : la primera palabra de la línea y a todos los blancos que la siguen.
Comando: diff <fichero1> <fichero2>
Misión: Encuentra las diferencias entre dos ficheros.
Ejemplo:
$ cat nombres1
jose
juan
roberto
$ cat nombres2
jose
roberto
$ diff nombres1 nombres2
2d1
< juan
Comando: join <fichero1> <fichero2>
Misión: mezcla ficheros clasificados
$ cat uno
antonio alperchines,5
juan seneca,32
maria oxigeno,45
pablo isaac peral,54
$ cat dos
antonio madrid
juan segovia
maria avila
pablo guadalajara
$ join uno dos
antonio alperchines,5 madrid
juan seneca,32 segovia
maria oxigeno,45 avila
pablo isaac peral,54 guadalajara
Comando: fold <-ancho-en-columnas> <fichero>
Misión: pasar palabras automáticamente a la línea siguiente. En el caso de tener un texto con líneas de más de 80 caracteres , al imprimirlo queda truncado. Usando éste comando , cualquie r texto que sobrepase el ancho establecido se pasa a la línea inferior.
Ejemplo:
fold -80 carta.txt | lp
Comando: sed ‘comandos de sed’ <ficheros>
Misión efectúa cambios en ‘fichero’ , enviando el resultado por la salida estándar. Pueden guardarse los resultados de éstas operaciones en un fichero como siempre , redireccionando la salida , tal que - sed ‘expresion’ fichero >/tmp/salida -.
Utilizaremos sed normalmente cuando sea necesario hacer cambios en ficheros de texto ; por ejemplo , nos puede valer para cambiar determinada variable en una serie de ficheros de profile de usuario , etc.
La explicación completa de éste comando exigiría mucho detalle ; veamos sólamente algunos ejemplos de operaciones con sed :
* Cambiar , en el fichero /Tutoriales/pepito/.profile , la expresión TERM=vt100 por TERM=vt220 . Usamos el comando ‘s/expresion_a_buscar/expresion_a_cambiar/g’ (s=search,g=global) :
sed ‘s/TERM=vt100/TERM=vt220/g’ /Tutoriales/pepito/.profile >/tmp/j && mv /tmp/j /Tutoriales/pepito/.profile
(si el comando ha sido OK , entonces movemos el /tmp/j).
* Meter tres blancos delante de todas las líneas del fichero /tmp/script.sh :
sed ‘s/^/ /’ /tmp/script.sh >/tmp/j && mv /tmp/j /tmp/script.sh
(el ^ identifica el principio de cada línea).
* (mas raro): Cepillarse todos los ficheros de un directorio :
ls | sed ‘s/^/rm -f /’ | sh
(del ls sale el fichero ; el sed le pone delante "rm -f" y el sh lo ejecuta.