4. Scripts básicos para bash

Indicadores de Logros

4.1. Lectura: Scripts para bash

Un script para bash es un archivo tipo texto, cuyas líneas tienen comandos que son ejecutados (interpretados) por bash. Para lograr que el intérprete de comandos intérprete las líneas de un archivo puede:

  • Ejecutar /bin/bash seguido del nombre del archivo (o redireccionar la entrada estándar para que provenga del archivo).

  • Emplear el comando source seguido del nombre del archivo.

  • Emplear el caracter '.' seguido de un espacio y el nombre del archivo.

  • Agregar en la primera línea del archivo la cadena #!/bin/bash, dar permiso de ejecución al archivo y teclear el nombre del archivo desde el intérprete de comandos ---como si fuera un nuevo comando.

En este capítulo presentamos algunas facilidades que bash brinda y que resultan muy útiles para escribir scripts. Antes se presentará como ejecuta bash una orden (lo cual en particulars explica porque la primera línea de un script para bash debería ser #!/bin/bash).

Dado que las facilidades que presentaremos no son exclusivas de scripts sino de bash, puede hacer experimentos desde el intérprete de comandos mientras lee.

4.1.1. Ejecución de un comando en bash

Para determinar cual es la orden bash realiza varias acciones:

  1. Primero realiza ciertas expansiones a la línea de comando, e identifica la orden, los parámetros y eventualmente las variables de ambiente que se den junto con la orden (en esta sección se estudiarán las expansiones y variables de ambiente).

  2. Si la orden va precedida de una ruta ---se trata del nombre completo de un archivo--- y el archivo existe y es ejecutable, bash lo trata como un programa y lo carga a memoria para ejecutarlo. Si la ruta no conduce a un archivo ejecutable bash presenta un mensaje de error.

  3. Si la orden no va precedida de una ruta, busca un alias que pueda corresponder con la orden y si lo encuentra lo remplaza por su valor (más adelante en esta sección explicaremos como manejar alias).

  4. Si la orden no esta precedida de una ruta y no es un alias, determina si se trata de un comando interno de bash (como fg o bg) y en caso de serlo realiza la acción correspondiente.

  5. Si la orden no está precedida de una ruta, no es un alias y no es un comando interno busca en varios directorios un archivo ejecutable con el nombre dado y si lo encuentra en alguno lo carga a memoria para ejecutarlo (el orden y los directorios donde busca se especifican en la variable de ambiente PATH que se explicará en esta sección).

  6. En otro caso bash presenta un mensaje de error.

En caso de que la orden corresponda a un archivo ejecutable (bien porque se dio la ruta completa o bien porque existe un archivo en un directorio de PATH), bash determinará como ejecutarlo:

  1. Si el archivo es tipo texto y comienza con la cadena "#!" seguida del nombre de un programa, bash emplea tal programa como intérprete del archivo. Como parámetros para el intérprete emplea los que estén en la primera línea del archivo, seguidos del nombre del archivo y a continuación otros parámetros que el usuario hubiera dado desde la línea de comandos.

  2. Si el archivo es tipo texto pero no comienza con #!, lo interpreta con el programa bash [44]. Es decir lo trata como un script para el intérprete de comandos.

  3. Si el archivo es binario lo envía al kernel para su ejecución. El kernel podrá ejecutarlo si está en un formato reconocido (e.g el ejecutable producido por un compilador en un formato reconocido por Linux ---ELF, a.out).

4.1.2. Ambiente y variables de ambiente

Una variable es un nombre al cual se le puede asociar un valor, tal valor puede cambiar durante la ejecución de un programa ---es 'variable'. Cada programa (incluyendo al intérprete de comandos) se inicia en un ambiente el cual consta de variables ---variables de ambiente--- que hereda del programa que lo inició y que pueden tener un significado especial para el programa. Las variables de ambiente que un programa usa se especifican en la página del manual del programa.

Para asignar un valor a una variable de ambiente y crearla si no existe, teclee el nombre de la variable, en seguida el carácter '=' y después el valor. El valor puede constar de letras, símbolos o números, pero tenga en cuenta que hay algunos caracteres con significado especial y que es mejor evitar en sus primeros experimentos: $, {, }, `, ' (podrá escribirlos precedidos de \ o encerrando el valor a asignar entre apostrofes). Por ejemplo para asignar el valor /home a la variable DIR:

DIR=/home

Después de asignar una variable, puede emplear el valor asociado a la misma en comandos que de al intérprete de comandos, para hacerlo emplee el nombre de la variable precedido del carácter '$'. Por ejemplo ls -l $DIR listará los archivos de la ruta asociada a la variable DIR.

En bash puede emplear el comando echo para enviar a salida estándar una cadena (por ejemplo echo tarea > porhacer.txt dejar en el archivo porhacer.txt una línea con la palabra tarea), esto puede usarse para examinar el valor de una variable de ambiente, e.g. echo $DIR presentará el valor de la variable DIR.

Puede examinar las variables de ambiente de bash con el comando set. Algunas de las variables que verá son: USER y USERNAME cuyo valor es el login del usuario; UID con el número que identifica al usuario; TERM mantiene el nombre de la terminal que está usando (ver Lectura Configuración de una sesión); SHELL la ruta y nombre del intérprete de comandos; PWD el nombre del directorio de trabajo; $HOME el nombre del directorio personal del usuario; PS1 y PS2 indican a bash como presentar prompts (see Lectura Configuración de una sesión); PATH es la ruta de directorios donde bash busca archivos ejecutables, se separan unas rutas de otras con el caracter ':'; OSTYPE el tipo de sistema operativo; MAILCHECK la frecuencia en segundos con la que bash debe revisar si ha llegado un nuevo correo a la cola de correos especificada en la variable MAIL (por defecto la del usuario); LS_COLORS colores que emplea el programa ls; LINES y COLUMNS indican la cantidad de filas y columnas de la terminal que está usando; LANG, LANGUAGE y otras variables que comienzan con el prefijo LC_ especifican el idioma en el que los programas deben interactuar con el usuario (ver Lectura Configuración de una sesión); HOSTNAME es el nombre del sistema; HISTFILE mantiene el nombre del archivo con la historia de comandos, su tamaño lo limitan HISTFILESIZE y HISTSIZE; DISPLAY mantiene la dirección del servidor X-Window (see Lectura Servicios de la Intranet).

Cuando se inicia un programa desde bash, el ambiente que tendrá constará de las variables que estén marcadas como exportables y de otras variables que se especifiquen al comienzo del comando (pueden separarse unas de otras con espacios y si el valor de alguna variable debe tener espacios puede encerrar el valor completo entre comillas), por ejemplo para iniciar el programa man en un ambiente con las variable LANGUAGE y LANG en el valor DE_de:

LANG=de_DE LANGUAGE=de_DE man man

Para exportar una variable y lograr así que forme parte del ambiente de procesos creados por su sesión, puede emplear bien declare -x VAR o export VAR, empleando el nombre de la variable que desea exportar en lugar de VAR. Empleando sólo export o sólo declare -x puede ver los nombres y valores de variables exportables.

En un script puede emplear ciertas variables especiales ($1, $2, ...) para referenciar los parámetros que el usuario empleó al iniciarlo. $1 tendrá el valor del primer paramétro, $2 del segundo y así sucesivamente. El siguiente script ejemplifica su uso:

#!/bin/bash

echo "Creando $2.tar.gz de $1"

mkdir $2
cp -rf $1/* $2
tar cvf $2.tar
gzip $2.tar

Este script recibe dos parámetros, el primero es una ruta y el segundo el nombre de un archivo. Si el nombre del script es comp y tiene permiso de ejecución podría usarse para crear un paquete comprimido d.tar.gz con el contenido del directorio ~/mand con:

./comp ~/mand d

Note que en el ejemplo indicamos la ruta completa del archivo comp, suponiendo que es ejecutado desde el mismo directorio donde se encuentra. Si la ruta donde está el archivo está en la variable PATH, no es necesario especificar la ruta.

4.1.3. Expansiones

bash trata algunos caracteres de forma especial: ' " { } $. Al asignar una variable o iniciar un comando bash "expande" estos caracteres y su contexto de varias formas :

$var - Expansión de variables o parámetros

El caracter '$' es empleado para distinguir variables o bien parámetros de un script. Las variables son remplazadas por su valor, por ejemplo echo $PATH presentará el contenido de la variable PATH. En un script los parámetros se referencian con números, $1 es el primero, $2 el segundo y así sucesivamente. Otros nombre especiales en un script son:

$#

Es remplazado por la cantidad de parámetros que el script recibe.

$*

Que se expande a todos los parámetros que el script haya recibido, un parámetro se separa de otro con el valor de la variable IFS que normalmente es un espacio.

$?

Todo programa al terminar debe retornar un número al sistema operativo, por convención 0 significa operacióne exitosa y números diferente representan errores. $? se expande al número retornado por el último programa ejecutado en primer plano. Un script puede retornar un 3 en lugar de 0 con exit 3

$-

Opciones que se pasaron al script durante su ejecución.

$$

Identificación del proceso del intérprete de comandos.

$!

Identificación del proceso del último comando que se ejecutó en segundo plano.

$0

Nombre del script o del shell.

"texto" - citas

Cuando un conjunto de caracteres (incluyendo espacios), se encierra entre comillas, bash los trata como una sola cadena. Esto es útil por ejemplo cuando el nombre de un directorio o archivo tiene espacios, e.g. cd "los amigos". Otra forma de representar el caracter espacio es con el caracter '\' seguido de un espacio, así el efecto del ejemplo anterior también podría lograrse con cd los\ amigos. Hay otros caractéres que pueden representarse con ayuda de '\', por ejemplo:

\n

Representa el caracter fin de línea.

\b

Caracter para borrar a la izquierda.

\\ \{ \} \$

Representan los caracteres '\' '{' '}' y '$' respectivamente. Esto es útil para producir estos caracteres sin que bash trate de interpretarlos --estos son caracteres para hacer expansiones.

\a

Caracter para emitir un sonido.

\t

Caracter tabulador (como la tecla Tab).

{letras} - expansión de corchetes

Una cadena que contenga { letra1, letra2, ... } será expandida a varias cadenas similares a la inicial pero la posición de {letras1, letra2, ...} será remplazada por cada una de las letras. Por ejemplo ls /home/juan/sal{a,e}n se expandirá a ls /home/juan/salan /home/juan/salen. Esta expansión es la primera que se realiza cuando hay varias en un mismo comando, y sólo surge efecto si está fuera de comillas o apóstrofes.

`comando` o $(comando) - sustitución de comandos

Un comando encerrado entre apóstrofes invertidos (i.e `comando`) o entre las cadenas "$(" y ")", será expandido al resultado que tal comando envíe a salida estándar cuando es ejecutado. Por ejemplo

TEXTOS=`ls *.txt`

asignará a la variable TEXTOS los nombres de los documentos tipo texto (i.e el resultado de ls *.txt).

ls $(cat rutas.txt)

presentará los archivos de los directorios que estén en el archivo rutas.txt.

'texto' - citas

Un texto que se encierra entre apóstrofes no es expandido. Esto es útil cuando se requiere una cadena que tiene algunos caracteres reservados para expansiones. Por ejemplo

N=10
echo '$N' es $N

enviará a salida estándar $N es 10

$((expresión)) - expansión aritmética

Una expresión aritmética [45] será evaluada cuando se encierre entre $(( y )), por ejemplo:

echo "1+2 es $((1+2))"
*, ?, ~ [letras] [rango(s)]- Expansión de rutas

Algunos caracteres y secuencias son expandidos a nombres de archivos. A continuación se presentan con ejemplos:

  • echo *, presentará todos los nombres de archivos del directorio de trabajo ---porque * expande a todos estos y echo los presenta---. En caso de que no haya archivos el caracter * no será expandido y será mostrado por echo.

  • echo datos?.gnumeric se expandirá a todos los nombres de archivos que comiencen por datos seguidos de un caracter arbitrario a su vez seguido de la cadena .gnumeric.

  • echo ~/.*errors presentará todos los nombres de archivos de configuración que estén en el directorio del usuario y que terminen con errors.

  • rm *[cho] eliminará todos los archivos que terminen con una de las letras o, h o c.

  • echo [0-9][a-z]* presentará todos los archivos que comiencen con un dígito seguido de una letra.

4.1.4. Comandos y programas útiles al hacer scripts

En un script puede emplear cualquier programa o comando, junto con redireccionamiento, procesos y control de tareas. bash ignora comandos que comienzan con el caracter '#'. Esto es útil para agregar comentarios explicativos a los scripts.

A continuación introducimos algunos comandos de bash y programas útiles al hacer scripts.

read

Lee una línea de entrada estándar y asigna las palabras a las variables que sigan al comando read. Puede especificarse un mensaje que se presentará como prompt antes de empezar a leer con la opción -p mensaje. El siguiente ejemplo lee dos palabras en las variables NOMBRE y APELLIDO:

read -p "Teclee nombre y apellido: " NOMBRE APELLIDO

Si la línea leída tiene más palabras que la cantidad de variables, el resto serán ignoradas. Si tiene menos palabras, las primeras variables (de izquierda a derecha) serán empleadas y el resto quedarán en blanco (i.e con la cadena vacía ""). En caso de que no se den nombres de variables, la línea leída quedará en la variable REPLY.

alias

Con este comando puede definir alias para comandos, después de definir un alias bash lo remplazará por el comando completo. Por ejemplo un alias para abreviar ls -l puede ser ll, que se definiría como:

alias ll="ls -l" 

Puede listar los alias definidos con la opción -p del comando alias.

unalias

Permite eliminar alias creados con el comando alias, por ejemplo para eliminar el alias ll se emplea:

unalias ll

Si se emplea la opción -a, el comando unalias elimina todos los alias definidos.

dirname

Recibe como primer parámetro el nombre completo de una archivo, incluyendo su ruta y envía a salida estándar sólo la ruta. Por ejemplo:

dirname /usr/doc/xterm/README.Debian

presenta en salida estándar /usr/doc/xterm.

basename

Análogo a dirname, pero envía a salida estándar el nombre del archivo.

hostname

Envía a salida estándar el nombre del computador en la red, esto también puede verse en la variable de ambiente HOSTNAME. Con la opción -f, el programa hostname presenta el nombre completo, con la opción -i la dirección IP, con la opción -d el dominio DNS, con la opción -a presenta nombres alternos de su máquina y con la opción -y el dominio NIS ---este programa extrae parte de la información del archivo /etc/hosts y de /etc/hostname.

whoami

Retorna el nombre del usuario que lo ejecuta. También puede verse en la variable de ambiente USER.

id

Retorna nombre y número del usuario que lo ejecuta, así como nombre y número del grupo o grupos a los que pertenezca. Con la opción -g sólo retorna el número del grupo principal, con -u retorna sólo el número del grupo principal, con -G retorna los números de todos los grupos a los que pertenece. Estas opciones pueden seguirse de n para presentar nombres en lugar de números.

declare

Las variables de ambiente tienen atributos que pueden examinarse o cambiarse con este comando interno de bash. Los atributos pueden ser sólo lectura (opción -r), exportable (opción -x) y variable entera (opción -i). Una variable de sólo lectura no puede ser modificada, y una variable entera sólo puede asociarse con números. [46]. Para examinar las variables que tengan un cierto atributo se emplea declare seguido del código de la opción, por ejemplo para examinar las variables con atributo de sólo lectura declare -r. Para cambiar un atributo a una variable se emplea la opción (precedida de - o + para activar y desactivar) seguida del nombre de la variable, por ejemplo declare -i N pone el atributo de variable entera a N.

4.2. Lecturas recomendadas: Scripts básicos para bash

  • Recomendamos de forma especial consultar la página del manual de bash. Hay algunas partes que complementan lo que se ha presentado en esta guía:

    • bash puede hacer más expansiones de parámetros, con la sintaxis ${...} ---por ejemplo sustituciones en el valor de una variable---. Puede consultar la información completa sobre este tema en la sección "expansión de parámetros".

    • Pueden hacerse scripts para bash con otras características que no se han presentado en esta guía: if, case, while, for, function.

  • El intérprete de comandos es una parte importante de la filosofía Unix, el libro clásico sobre este tema es de Brian. W. Kernighan y Rob Pike "El Entorno de Programación UNIX", Prentice-Hall Hispanoamericana. Este libro explica y muestra el uso de csh, un predecesor de bash ---la mayoría de características de csh están disponibles en bash.

  • En Internet puede consultar "Advanced Bash-Scripting Guide: A complete guide to shell scripting, using Bash" de Mendel Cooper, disponible en: http://linuxdoc.org/LDP/abs/html/index.html o Programación en BASH - COMO de introducción http://lucas.hispalinux.es/COMO-INSFLUG/COMOs/Bash-Prog-Intro-COMO/Bash-Prog-Intro-COMO.html

4.3. Ejercicios: Scripts básicos para bash

4.3.1.

Cree un script que sea interpretado por bash, que al ejecutarse busque entre sus directorios, todos los archivos, ordene los nombres alfabéticamente y le envíe un correo con esa información. Ayuda: Puede emplear la opción -R de ls para listar subdirectorios, y la opción -u de sort.

4.3.2.

La variable de ambiente HOME contiene el nombre de su directorio personal. Comprúebelo y después empleela para cambiarse a su directorio. Después cambie esta variable y explique que ocurre con el comodín '~'.

4.3.3.

El programa man presenta páginas del manual, empleando el programa especificado en la variable de ambiente PAGER o en su defecto con el programa less (ver Lectura bash y el juego de herramientas). Modifique y exporte la variable PAGER para que man emplee el programa more para presentar información y pruebe el cambio.

4.3.4.

¿Qué hace el comando echo `ls` ? (note que se usan apóstrofes invertidos).

4.3.5.

¿Qué hace el comando N=6 echo "'1+$N' = $((1+$N))" ? Después de dar su respuesta, compruébela empleando un intérprete de comandos.

4.3.6.

Haga un script que cada 90 minutos presente el mensaje "Hacer tareas". Ayuda: source.

4.3.7.

Cree un script que al ser ejecutado, cree un archivo script2. El archivo script2 creado debe poner la variable PATH en el valor que tenga cuando el primer script sea ejecutado, añadir a tal variable la ruta /opt/bin y debe crear un alías que cuando se ejecute muestre el contenido de la variable PATH (no olvide cambiar el modo del archivo script2 generado para que sea ejecutable).

4.3.8.

Haga un script que después de ejecutarse pida al usuario 2 números y después presente la suma, la resta, el producto y la división.



[44] En rigor lo interpreta con /bin/sh pero en Debian /bin/sh es un enlace a /bin/bash

[45] En la expresión pueden emplearse los operadores - (unario y binario), + (unario y binario), ** para elevar a una potencia, * para multiplicar, / para divir, % para obtener residuo de una división. Hay operadores que operan a nivel de bits (representación binaria de los números que operan): ~ para negar bits, & para hacer "Y" entre bits de operandos y | para efectuar "O". También pueden emplearse valores booleanos --- 0 se interpreta como falso y 1 como verdadero---, pueden compararse números con los operadores >, <, <= (menor o igual), >= (mayor o igual), == (igualdad), != (diferentes) y pueden operarse booleanos con ! para negar, && como conjunción (Y) y || como disyunción. Los números pueden escribirse en decimal, o en otras bases empleando como prefijo del número la base seguida del caracter '#' (también puede escribirse números en octal iniciándolos con 0 o en hexadecimal iniciándolos con 0x).

[46] Otros atributos pueden ser "función" -f y arreglo -a