Programación Bash Shell

De Documentacion NexuN

Ahora que sabemos manejarnos con el shell y conocemos unos pocos comandos, vamos a comenzar a hacer pequeños programas que interpretará el shell. En esta parte necesitaremos un editor de texto plano, como pueden ser: vi, emacs, joe, mcedit, nano, kwrite, gedit, etc. Cualquiera de ellos vale, siempre que guardemos el texto como text/plain.

Contenido

[editar] El primer script !

Vamos a crear un fichero, script.sh, con el siguiente contenido:

#!/bin/sh
echo Hola Mundo!

Intentamos ejecutarlo con ./script.sh y no funciona. Esto es porque la extensión sh no es lo que hace que sea ejecutable. Para que se pueda ejecutar tenemos que darle permisos de ejecución:

$ chmod +x script.sh

Ahora sí es ejecutable. El programa consta de dos lineas:

   * Un comentario, desde la aparición de # hasta el final de esa linea, donde se indica al shell con la secuencia #!/bin/sh que /bin/sh es el programa que se debe usar para ejecutar este fichero.
   * Un comando que muestra un texto en stdin. Es la primera linea "ejecutable", ya que los comentarios son ignorados por el intérprete.

Otro ejemplo de script, muchas personas manejan bases de datos en diferentes tipos de archivos y programas, por ejemplo un manejador de bases de datos MySQL, por algún motivo inesperado podrían pasar muchas cosas, este script es para respaldar la información:

#!/bin/sh
mysqldump -u <user> -p<pass> rocar --opt > /home/backup/MySQLBackUp/rocar.sql
cd /home/backup/MySQLBackUp
tar -zcvf respaldosql_$(date +%d%m%y).tgz *.sql
find /home/backup/MySQLBackUp/ -name '*.tgz' -type f -mtime +2 -exec rm -f {} \;
rm rocar.sql

La primer linea como saben es la tipica de un script, la segunda linea ocupa el mysqldump con el logueo de el usuario , cabe destacar que despues de -u hay un espacio para el nombre de usuario, y despues de -p no, debe ponerse la contraseña seguida de ese parametro, de lo contrario no sera reconocida por el sistema, despues un espacio para la abse de datos , luego un modificador y le decimos que el resultado de ese comando lo guarde en la ruta descrita con el nombre rocar.sql en la tercer lineanos movemos al directorio donde se guardo el respaldoen la cuarta linea comprimimos el respaldo para ahorrar espacio, llamandolo respaldosql_ con extension tgz la quinta linea sinceramente no se que haga jajaja si alguien sabe que me diga xD y la ultima linea elimina el respaldo que hizo mysqldump y solo deja el respaldo ya comprimido.

Este al igual que el anterior ejemplo se le da permiso de ejecución

chmod +x script.sh

probamos manualmente para ver que funcione

./script.sh

[editar] Variables

Una variable es un contenedor. Consta de un identificador que la distingue de otra (su nombre) y de un contenido. La relación entre variable y contenido es de equivalencia. Por lo general las variables en shell no tienen tipos asociados y se definen de la siguiente forma: identificador = contenido Ejemplos:

# i vale 1
i=1
# I vale echo
I=echo
# msg vale Hola mundo!
msg="Hola mundo!"

Cuidado: si dejamos espacios entre el = y el identificador o el valor el shell creerá que son comandos a ejecutar y no la asignación de una variable. Para acceder al contenido de una variable empleamos $ delante de su identificador: $identificador Ejemplos:

$ i=1 ; echo $i
$ msg="Mola mundo!" ; echo $msg
$ fu=echo; $fu goo!

Cuando empleamos $identificador el shell busca el valor almacenado en la variable asociada a ese identificador y lo utiliza para reemplazar esa ocurrencia de $identificador. Muchas veces desearemos asignar la salida de un comando a una variable, por ejemplo, para obtener el usuario que ejecuta el script. El comando "whoami" nos devuelve el nombre del usuario.

#!/bin/bash
usuario=$(whoami)
echo "hola $usuario"

[editar] Expansión de Variables

Podemos obtener más datos sobre las variables, por ejemplo, cuántos carácteres tiene una variable:

  • ${v} Idéntico a $v. Se utiliza en los casos de ambigüedad
  • ${#v} Número de caracteres de la cadena que contiene v
  • ${#v[*]} Número de elementos del vector v (Después Explicamos lo del vector)
  • ${v­c} Si v ha sido definida su valor, en otro caso retorna c
  • ${v=c} Si v ha sido definida su valor, en otro caso le asigna ahora el valor c
  • ${v+c} Si v ha sido definida c, en otro caso la cadena vacía

[editar] Variables más comunes

PPID Número de identificación del proceso padre de la interfaz de comandos.

PWD Directorio de trabajo actual, establecido por el comando cd.

OLDPWD Anterior directorio de trabajo, establecido por el comando cd.

REPLY Cuando se usa el comando read de bash sin argumentos,esta variable recoge el contenido de la línea leída.

UID Número identificativo (ID) del usuario, establecida en el arranque del shell.

EUID ID eficaz de usuario, inicializada en el arranque del shell.

BASH Nombre completo, incluida la ruta, del archivo que se ejecutó para lanzar el shell.

BASH_VERSION Número de versión de Bash.

SHLVL Nivel del shell. Incrementa su valor en una unidad cada vez que se lanza una nueva interfaz de comandos.

RANDOM Cada vez que se invoca a esta variable se obtiene un número entero aleatorio. La secuencia de números aleatorios proporcionadas por RAMDOM se puede inicializar simplemente asignándole un nuevo valor a esta variable.

SECONDS Mantiene el número de segundos que han transcurrido desde que se ejecutó el shell. Si asignamos un valor a esta variable, la cuenta indicará los segundos transcurridos desde dicha asignación, mas el valor asignado.

LINENO Cuando se hace referencia a esta variable desde un script, indica la línea dentro del script en la que se le esta haciendo referencia, considerando que la primera línea se numera como 1. Si se invoca desde la propia interfaz de comandos, el valor que devuelve es el número de línea que hace la orden ejecutada desde que se inició la interfaz de comandos.

HISTCMD Contiene la posición dentro del archivo de historia de comandos que ocupa el comando actual.

HOSTTYPE Se trata de una cadena de texto describiendo el tipo de máquina en la que esta ejecutando el bash.

HOSTNAME Nombre de la máquina.

OSTYPE Se trata de una cadena de texto describiendo el sistema operativo en el que sé esta ejecutando el bash.

PATH La ruta de búsqueda de comandos. Se trata de una secuencia de directorios en los que localizar los programas, separados entre sí por un signo de dos puntos ( : ). La interfaz de comandos recorrerá esta lista en el orden dado buscando los comandos que queramos ejecutar.

HOME Directorio raíz del usuario actual. Es el argumento usado por defecto cuando ejecutamos el comando cd sin especificar ninguno.

CDPATH Se trata de la ruta de búsqueda para el cd. Tiene una estructura similar a la variable PATH. Lo que indica es donde se deben buscar los directorios especificados como argumentos al comando. Como ejemplo habitual, podría contener :~:/usr.

MAIL Cuando contiene el nombre de un archivo, bash comprueba en él la llegada de correo nuevo y avisa en caso que se produzca.

MAILCHECK Determina el intervalo de tiempo en segundos que bash tomará para revisar si hay correos nuevos.

MAILPATH Al igual que PATH y CDPATH, esta variable contiene una lista, en este caso, de archivos que deberán ser comprobados para la posible llegada de correo. Se puede indicar un mensaje especifico para la llegada de correo en diferentes buzones usando el carácter '?' como separador entre el archivo y el mensaje. En ese caso, la variable $_ obtendrá el nombre del buzón. Un ejemplo: MAILPATH='/var/spool/mail/nlucas?"Tienes correo":~/mail/buzon?"Ha llegado algo al $_"'

PS1 Valor del prompt principal. En la tabla 1 se muestran los valores con los que puede expandirse.

PS2 Valor del prompt secundario. Este prompt es el que aparece cuando partimos una línea en la interfaz de comandos para continuar introduciendo en la siguiente línea de la pantalla.

PS3 Valor del tercer prompt. Este prompt es usado solamente por el comando select.

PS4 Valor del cuarto prompt y último. Tan solo se usa cuando se esta realizando un seguimiento de los comandos mostrándose para indicar los pasos por los que se va ejecutando el comando. Para entrar en el modo de seguimiento, basta con ejecutar set -x. Entonces veremos como cada comando que ejecutemos se expande, mostrando las sustituciones que se hacen con los alias, las variables, etc.

HISTSIZE Contiene él número de comandos a guardar en el historial de comandos.

HISTFILE Contiene el nombre del archivo en el que se almacena el historial de comandos. Por defecto se trata del archivo ~/.bash_history.

HISTFILESIZE Número máximo de líneas que puede contener el archivo de historial. Tengamos en cuenta que un comando puede ocupar varias líneas.

PROMPT_COMMAND Si esta definido, contiene el comando a ejecutar antes de presentar el prompt.

IGNOREEOF Indica cuantas veces ha de recibir el carácter EOF (End Of File, o la pulsación de la tecla Crtl-D) antes de salir de bash. Si el valor indicado no es numérico, se toma por defecto el 10. Si no esta seleccionado, una única pulsación basta.

TMOUT Si contiene un valor superior a cero, indica el número de segundos que se puede estar sin introducir un comando a el shell. Pasado este tiempo, la interfaz de comandos se cerrará.

FCEDIT Ruta y nombre del editor usado por defecto para el comando fc. Por defecto se usa Vi .

FIGNORE Lista de sufijos de archivos que se ignoraran al intentar completar un nombre de archivo desde bash. La lista estará formada por los sufijos ignorados, separados por un signo de dos puntos ( : ). Por ejemplo .0:.tmp

EDITOR En esta variable muchos programas buscaran la ruta y el nombre del editor a usar. Por defecto el editor usado es el Vi. Algunos de los programas que hacen uso de esta variable son crontab (con la opción -e), edquota y otros muchos.

TERM Contiene el tipo de terminal. Esta variable es importante, pues algunos tipos de terminales no son compatibles con algunos programas.

[editar] Linea de comandos

Cuando se ejecuta nuestro programa en shell hay una serie de variables que siempre estarán disponibles, entre ellas las que nos permiten acceder a los distintos argumentos con los que fue ejecutado nuestro script. $0 contiene el nombre nombre de nuestro script $# el número de parámetros con los que se ha invocado al shell $n los parámetros, con n de 1 a 9 (a $#) $$ el PID de nuestro proceso $* todos los parámetros menos $0

[editar] La salida de los programas

Cuando se ejecuta un programa, un comando UNIX es un programa, podemos, a parte de redirigir su entrada y su salida, recoger el resultado de su ejecución y su salida. El resultado es un valor numérico, por lo general cero si todo ha ido bien, y distinto de cero si ha habido alguna clase de error. La salida del programa es lo que obtendríamos en stdin y stdout. $? resultado del último programa ejecutado Ejemplo:

$ ls pirulotropical 2> /dev/null ; echo $?
$ ls > /dev/null ; echo $?
$(comando)

la salida de comando (esto es equivalente al uso de comillas invertidas, pero por simplicidad vamos a utilizar esta versión) Ejemplo:

$ salida_ls=$(ls) ; echo $salida_ls
exit ENTERO

termina nuestro programa con el valor de salida ENTERO

Ejercicio 1: realizar un script que dado un directorio, cree un archivo tar comprimido con gzip y con nombre igual a la fecha en formato yyyy-mm-dd seguido del nombre del directorio acabado en .tar.gz. Ejemplo: aplicado sobre tmp obtendríamos -> 2004-04-03tmp.tar.gz.

[editar] Operaciones aritméticas

Para que el shell evalue una operación aritmética y no la tome como argumentos de un comando, por ejemplo:

$ echo 1+1

Si queremos que sustituya la operación por su valor emplearemos:

$((expresión))

evalua la expresión aritmética y reemplaza el bloque por el resultado Ejemplo:

$ echo $((1+1))

Algunos operadores aritméticos soportados: + suma

  • mutiplicación

- resta / división entera % resto de la división entera () agrupar operaciones

Ejercicio 2: realizar un script que dado un número 'n' muestre los diez primeros elementos de su tabla de multiplicar, mostrando el resultado en la forma: i x n = resultado.

[editar] Condicionales

Existe un comando para evaluar condiciones, y que nos permitirá que nuestros programas "tomen decisiones": test expresion [ expresion ]

Este comando evalua expresion, y si evalua a cierto, devuelve cero (true), o en otro caso 1 (false). Si no hay expresión, test siempre devuelve falso. Este comportamiento puede ser algo confuso, ya en lógica los valores cierto y falso suelen ser al contrario. test soporta gran cantidad de operadores, algunos son: -d fichero cierto si fichero existe y es un directorio -e fichero cierto si fichero existe, independientemente del tipo que sea -f fichero cierto si fichero existe y es un fichero normal -r fichero cierto si fichero existe y se puede leer -s fichero cierto si fichero existe y tiene tamaño mayor que cero -w fichero cierto si fichero existe y es se puede escribir sobre él -x fichero cierto si fichero existe y es ejecutable n1 -eq n2 cierto si los enteros n1 y n2 son iguales n1 -ne n2 cierto si los enteros n1 y n2 no son iguales n1 -gt n2 cierto si el enteros n1 es mayor que n2 n1 -ge n2 cierto si los enteros n1 y n2 son iguales o n1 es mayor que n2 n1 -lt n2 cierto si el enteros n1 es menor que n2 n1 -le n2 cierto si los enteros n1 y n2 son iguales o n1 es menor que n2 s1 = s2 cierto si las cadenas de texto s1 y s2 son idénticas s1 != s2 cierto si las cadenas de texto s1 y s2 no son idénticas s1 < s2 cierto si la cadena de texto s1 es menor que s2 s1 > s2 cierto si la cadena de texto s1 es mayor que s2 -n cadena cierto si la longitud de la cadena de texto es distinta de cero ! expresion cierto si expresion es falsa (negación) expresion1 -a expresion2 cierto si expresion1 y expresion2 son ciertas expresion1 -o expresion2 cierto si expresion1 o expresion2 son ciertas

Además existen los operadores lógicos && (AND, multiplicación lógica) y || (OR, suma lógica), que se puede aplicar al valor de salida de los programas: $ true && true ; echo $? $ true && false ; echo $? $ false && true ; echo $? $ false && false ; echo $? $ true || true ; echo $? $ true || false ; echo $? $ false || true ; echo $? $ false || false ; echo $?

El sistema de evaluación del shell es perezoso y va de izquierda a derecha. Si se encuentra la suma lógica true || ALGO, ALGO no se evaluará porque se asume que cierto o falso o cierto o cierto siempre es cierto .

[editar] if ... then ... else ... case

Esta es la principal estructura que nos permitirá ejecutar un bloque de código, o (alternativamente) otro, dependiendo de como se evalue una condición. if CONDICION; then bloque de comandos fi if CONDICION; then bloque de comandos b1 else bloque de comandos b2 fi

En el primer caso el bloque de comandos se ejecutará solo si la condición es evaluada a cierto. En el segundo caso el bloque b1 se ejecutará si la condición es evaluada a cierto, y sino se ejecutará el bloque b2. La condición puede ser, por ejemplo, una llamada al comando test o una operación lógica entre los valores de salida de diferentes comandos.

read linea
# comparamos cadenas de texto, así que usamos comillas
if [ "$linea" = "secreto" ]; then
echo bingo!
fi
if ! $(ping -c 1 192.168.0.100 > /dev/null); then
echo La máquina 192.168.0.100 no responde
else
echo La máquina 192.168.0.100 está ahí!
fi


Es para formar una estructura selectiva, la estructura selectiva evalúa la condición de control y, dependiendo del resultado, ejecuta un bloque de código determinado.

case $VARIABLE in
opcion 1) 

accion 1
;;
opcion 2)
accion 2
;;
opcion 3)
accion 3
;;
esac

Otro ejemplo

# Según el valor de la variable UTC:
# - si es “yes” o “true”, ...
# - si es “no” o “false”, ...
case "$UTC" in
yes|true) CLOCKFLAGS="$CLOCKFLAGS --utc";
CLOCKDEF="$CLOCKDEF (utc)";
;;
no|false) CLOCKFLAGS="$CLOCKFLAGS --localtime";
CLOCKDEF="$CLOCKDEF (localtime)";
;;
esac

Y, como en los casos anteriores, describir el modo de ejecución de la siguiente estructura selectiva:

case "$SO" in
AIX) echo –n "$US: "
lsuser -ca expires $US|fgrep -v "#"|cut -f2 -d:`"
;;
SunOS) echo "$US: `logins -aol $US|cut -f7 -d:`"
;;
Linux) echo "$US: `chage -l $US|grep Account|cut -f2 -d:`"
;;
*) echo "Sistema operativo desconocido" ;;
esac 

Ejercicio 3: realizar un script que, dado un número, indique si es o no divisible entre 101. Si no se proporciona un número debe mostrar como usar el programa.

[editar] Bucles

El shell aporta mecanismos para realizar tareas repetitivas mediante el empleo de estructuras que permiten repetir un bloque de comandos. La instrucción while, sirve para hacer un bucle infinito lo único que se tiene que hacer es que la condición que evalúe while sea siempre cierta, por ejemplo while true.

#!/bin/bash
 
while true ; do
 clear
 echo
 echo "0. Salir"
 echo "1. Ver contenido de directorio"
 echo "2. Ver directorio"
 read -p "Entra una opcion (0,1,2): " opcion
 case $opcion in
 0) break ;;
 1) ls
 echo "<<Pulsa ENTER para continuar>>"
 read ;;
 2) pwd
 echo "<<Pulsa ENTER para continuar>>"
 read ;;
 esac
done
echo "Fin".

[editar] for ... in ...

Esta estructura permite repetir un bloque de comandos asignando valores de una serie a una variable en cada iteración.

for VARIABLE in SERIE; do bloque de comandos done

En cada iteración la variable VARIABLE toma un valor de SERIE, que en caso de no contener elementos hará que no se ejecute nada y se devuelva un valor 0. En caso de que se ejecuten comandos, el resultado devuelto tras el bucle es el del último comando ejecutado. Ejemplos de bucle:

# equivalente a seq 1 5
for i in 1 2 3 4 5; do
echo $i
done
# lo mismo pero con palabras
for palabra in uno dos tres cuatro cinco; do
echo $palabra
done

Ejercicio 4: realizar un script que dado un número 'n' muestre los diez primeros elementos de su tabla de multiplicar, mostrando el resultado en la forma: i x n = resultado. Emplear un bucle y seq (si está disponible). Si no se proporciona un número, mostrar como se usa el programa.

Ejercicio 5: realizar un script que dado una lista de directorios, cree un archivo tar comprimido con gzip con nombre igual a la fecha en formato yyyy-mm-dd.tar.gz. Además se generará un fichero yyyy-mm-dd.lst con los nombres de los directorios contenidos en el archivo tar, UNO POR LINEA usando un bucle. Si el fichero lst existe, mostrar un error y terminar el programa. Si alguno de los elementos no es un directorio, mostrar un error y finalizar el programa.

[editar] Rompiendo un bucle: break

En cualquier momento un bucle puede ser interrumpido mediante el uso de break, de forma que tras ser ejecutado ese comando el control pasa al siguiente comando después del done. Ejemplo de uso de break:

for elemento in *; do
echo Primer elemento $elemento
break
echo Esto nunca se llega a ejecutar
done
echo Seguimos con el programa

[editar] while ...

Se trata de otra estructura de bucle que permite ejecutar un bloque de comandos mientras se evalue una condición a cierto: while CONDICION; do bloque de comandos done

Cada iteración se evalua la condición y en el momento que no sea cierta, el bucle termina.

Ejemplos de bucles:
# equivalente a seq 1 5
i=1
while [ $i -lt 6 ]; do
echo $i
i=$(($i+1))
done
# lee de stdin hasta que se introduzca 'quit'
read linea
while [ "$linea" != "quit" ]; do
read linea
done

Ejercicio 6: realizar un script que permita adivinar al usuario cual es su PID. El script pide un número al usuario y cada vez que lo haga debe indicar al usuario si el PID es mayor o menor que el número introducido. Cuando se adivina el valor, se deben mostrar los intentos empleados.

Soluciones