17 años en Internet

14 julio 2011

Apuntes de shell script

- Emplear argumentos:
Al igual que en C o en Java, a la hora de invocar una shell puedes pasar argumentos. Por defecto en Bash  la variable $0 equivale al nombre del binario, el $1 al primer argumento, el $2 al segundo... y así hasta $n. Ejemplo:
./fprintf.sh Hola
Donde el código del script sería:
#!/bin/bash
echo $1

- Definir y emplear variables:
Para crear una variable basta con poner el operador "=" después del nombre. Por defecto todos los valores que asignes serán strings, pero Bash es lo suficientemente inteligente para saber diferenciar strings de enteros:
nombre=Sebas
edad=28
argumento=$1
No obstante, siempre que queramos emplearlas deberemos de poner delante el símbolo $:
echo "Hola ${nombre}. Tienes $edad años"
Cabe destacar que por defecto en un script de Bash todas las variables son locales. Es decir, distintas instancias de una shell pueden tener distintos valores para una misma variable. Además, una vez finalice el script esta variable no aparecerá en la lista de variables inicializadas en el sistema. Si necesitas crear una variable "global" que pueda ser empleado en otros scripts del sistema, tendrás que usar el comando export:
export nombre=Sebas 

- Realizar cálculos:
Para realizar operaciones simples (como sumar o restar enteros) podemos emplear el comando let. Su funcionamiento es simple:
operador_A=1
operador_B=2
let resultado_suma=${operador_A}+${operador_B}
Si por el contrario necesitas emplear decimales, o realizar mutiplicaciones o divisiones, tendrás que emplear awk:


division=`awk -v dividendo=$dividendo -v divisor=$divisor  'BEGIN{printf "%d",dividendo/divisor}'`
division_con_decimales=`awk -v dividendo=$dividendo -v divisor=$divisor  'BEGIN{printf "%.2f",dividendo/divisor}'`


- Manejar strings:

Si os fijáis en el ejemplo echo "Hola ${nombre}. Tienes $edad años", he puesto nombre entre llaves. ¿Por qué? Esto es porque Bash piensa que la varaible que quieres emplear no es "nombre", si no "nombre." y al no existir esta variable Bash nos printaría sólo por pantalla "Hola  Tienes 28 años". Es decir, cuando veas que el nombre de una variable pueda estar comprometido debes emplear llaves para que Bash sepa cuando empieza y cuando acaba el nombre de variable. Esto nos permite, por ejemplo, componer nuevos strings a partir de otros ya existentes:

nombre_maximizado="Hola ${nombre}món"

También podemos recortar un string ponínendolo entre llaves e indicando dos valores: el caracter inicial que nos interesa y la longitud que tendrá el substring:
nombre_recortado=${nombre:0:3}
iniciales=${nombre:0:1}.${apellido1:0:1}.${apellido2:0:1}

- Utilizar condiciones:
Todo el que sepa programar no tendrá problemas con esto:
if [ condición ]
then
(...)
else
(...)
fi
Bash emplea muchos operadores para analizar condiciones. Os resumo los más comunes: -eq para comprobar que un entero es igual a otro; -lt para comprobar que un entero es menor a otro; -gt para comprobar que el entero es mayor al otro; == para comprobar que dos strings son iguales; Y por último ! al principio para negar el resultado. Unos ejemplos:

if [ $edad -gt 100 ]
then
echo "Es un milagro que sigas vivo"
fi


if [ ! $nombre == "Sebas" ]
then
echo "No eres Sebas"
fi


Si elaboras scripts de gran tamaño, a lo mejor te conviene revisar que las variables que empleadas están inicializadas. Esto se puede hacer con una negación lógica del contenido de la variable:
if [ ! $edad ]
then
echo "La varibale edad no está inicializada"
edad=18
fi
Nota: si trabajas con variables que contienen decimales puedes tener problemas a la hora de manejar condiciones. O bien empleas awk (como hicimos para calcular divisiones), o bien la entrecomillas la variable para tratarla como un string:

if [ ! "${nota_media}" ]
then
nota_media=0.00
fi



- Asignar valores a variables a partir de resultados de comandos:
Con la comilla invertida podemos invocar comandos dentro de una shell y asignar a una variable el valor de su salida estándar. En este ejemplo la variable resultado_ls tendrá como valor el archivo txt más reciente del directorio actual:
resultado_ls=`ls -lrt *txt | tail -1`
Si necesitamos almacenar un valor de un comando dentro de una comilla invertida, podemos utilizar la comilla simple:
resultado_ls=`ls -lrt *'echo ${fecha}'.txt | tail -1`

- Arrays:
Sí, en Bash también existen vectores. No hace falta definir ninguna talla, símplemente defines índices al vuelo:
palabra[0]=Hola
palabra[1]=estimado
palabra[2]=lector
numero=2
echo "${palabra[${numero}]}"

- Bucles:
Bash emplea los dos bucles más famosos: while y for.
while [ condición ]
do
(...)
done


for ((  i = 0 ;  condición ;  i++  ))
do
(...)
done

Ejemplos:


while [ ! "$j" == "AAAA" ]
do
j=${j}A
done



for ((  i = 0 ;  i <= 50 ;  i++  ))
do
echo  "-"
done

while true
do
df -h
sleep 1
clear
done



- Leer un fichero línea a línea:
Basta con generar un bucle while facilitando la ruta del archivo como entrada estándar.
while read linea
do
nombre=`echo ${linea}`
(...)
done < /tmp/mi_lista_de_nombres


- Manejar señales:
Si eres un entendido en señales posix entonces quiere decir que eres un maestro del C puro. Que sepas que todo el tema de máscaras y manejadores en shell script se resume en un sólo comando: trap. Esta sentencia te permite invocar comandos o funciones cuando el proceso recibe la señal que le indiques:
trap "echo Noooooooo" INT
trap "rm $archivos_temporales" EXIT

En este caso, cuando recibimos un control+c de teclado nuestro shell script mostrará por su salida estándar el texto Noooooooo. Si por el contrario finaliza el script este borrará antes todos los archivos temporales que tenemos definidos en la variable archivos_temporales.


- Lanzar comandos en segundo plano:
Pon entre paréntesis la lista de comandos que quieres que trabajen en segundo plano. Esto significa que el "proceso hijo" definido entre paréntesis trabajará de forma concurrente con el proceso padre (tu shell script). Detrás del paréntesis de cierre debes poner un &. Ten en cuenta que si no aplicas redirecciones la salida estándar de los procesos hijo serán la misma que la del proceso padre.
echo "Calculamos las notas de 2009 y 2010"
(calcular_notas.sh 2009 ; echo "FIN calculo notas 2009") &
(calcular_notas.sh 2010 ; echo "FIN calculo notas 2010") &

3 comentarios:

  1. Muy buenos ejemplos, me habría gustado ver tu "pequeño" debate sobre el bash que comentabas en el post anterior :)

    ResponderEliminar
  2. Creo que ahora deberías aprender perl, es como bash+awk+sed+grep+sockets ip en el mismo lenguaje y usando la misma nomenclatura. Con la ventaja de que lo que escribas no lo entenderá nadie.

    ResponderEliminar
  3. Alguna pijería he hecho en Perl, pero a mi jefe no le gusta que lo gaste =(

    ResponderEliminar

Si te ha gustado la entrada o consideras que algún dato es erróneo o símplemente deseas dar algún consejo, no dudes en dejar un comentario. Todo feedback es bienvenido siempre que sea respetuoso. También puedes contactarme vía Twitter @Hamster_ruso si lo consideras necesario.