Programación y scripts Bash
Introducción
Este capítulo explica cómo automatizar la ejecución de trabajos a partir de los elementos estudiados anteriormente. Las funcionalidades abordadas permitirán al usuario crear sus propios scripts de shell, programas desarrollados en lenguaje shell.
Además, el lector podrá comprender y personalizar los muchos scripts ya proporcionados con el sistema Linux.
Scripts shell
Un script Bash es un archivo de texto que contiene una serie de comandos como los ejecutados en la línea de comandos.
Toda línea de comandos válida puede ser objeto de un script de shell para un tratamiento por lotes (batch). Y viceversa, todo el código escrito en un script Bash puede escribirse directamente en la línea de comandos.
En este ejemplo, el archivo de script se llama prog.sh:
[javier]$ cat prog.sh
date
whoami
var=12
echo $var
1. Llamada y ejecución
Las modificaciones del entorno del shell que llama dependen de la forma como se llama al script shell.
bash prog.sh
La primera forma de llamar a un script shell consiste en lanzar un nuevo bash con el nombre del script como argumento. Este nuevo shell trata las instrucciones contenidas en el archivo como si se hubieran escrito en el teclado.
De ello resulta la creación de un subproceso bash que termina automáticamente al finalizar la ejecución del script.
[javier]$ cat prog.sh
date
whoami
var=12
echo $var
[javier]$ echo $var
[javier]$ bash prog.sh
mie sep 5 15:46:25 CEST 2018
javier
12
[javier]$ echo $var
Toda modificación del entorno -como el cambio de directorio o la definición de variable- sólo interviene en el subshell creado temporalmente. No se modifica el shell inicial.
Se puede representar este tipo de script así:
El primer shell iniciado en un terminal (llamado shell de conexión) lleva como prefijo un guión en la lista de procesos. Por esta razón, su nombre es -bash en el esquema anterior, mientras que el subshell se llama bash.
El shell bash es un subproceso del shell de conexión -bash. Asimismo, los comandos externos date y whoami son subprocesos del shell bash mientras que la asignación...
Códigos de retorno
Todo comando en Linux devuelve un código al terminar su ejecución. Este código asegura el correcto desarrollo de la ejecución: cero cuando el programa ha terminado correctamente, diferente de cero en caso contrario.
El código de retorno de un comando es un valor comprendido entre 0 y 255.
Un mismo comando puede devolver diferentes códigos si su ejecución termina mal, por diversas razones. Por ejemplo, el comando cp puede fallar porque el archivo fuente no existe, los permisos de escritura del directorio de destino son insuficientes, el sistema de archivos está saturado, etc.
La variable especial $? contiene el código de retorno del último comando ejecutado:
[javier]$ pwd
/home/javier
[javier]$ echo $?
0
[javier]$ ls zorglub
ls: zorglub: No such file or directory
[javier]$ echo $?
1
[javier]$ echo $?
0
El último código de retorno es cero porque indica que el comando anterior (echo $?) se ha llevado a cabo bien. Para reutilizar ulteriormente el código de retorno de un programa, hay que guardarlo en una variable:
[javier]$ ls zorglub
ls: zorglub: No such file or directory
[javier]$ cr=$?
[javier]$ pwd
/home/javier
[javier]$ echo $?
0
[javier]$ echo $cr
1
De modo...
Encadenamiento de comandos
1. Ejecución secuencial
En lugar de escribir los comandos uno tras otro y esperar el fin de su ejecución antes de ejecutar el siguiente, es posible encadenar varios comandos en la misma línea, separándolos por punto y coma:
[javier]$ date
mie sep 5 15:49:00 CEST 2018
[javier]$ ps
PID TTY TIME CMD
4511 pts/1 00:00:00 bash
4613 pts/1 00:00:00 ps
[javier]$ date ; ps
mie sep 5 15:49:01 CEST 2018
PID TTY TIME CMD
4511 pts/1 00:00:00 bash
4624 pts/1 00:00:00 ps
Estos encadenamientos son prácticos cuando deben lanzarse sucesivamente varios comandos largos, sin que el usuario tenga que intervenir.
Los espacios alrededor del ; en la línea de comandos no son obligatorios, pero mejoran la legibilidad.
En el encadenamiento cmd1 ; cmd2 ; cmd3, el comando cmd2 se ejecuta al terminar el comando cmd1; igualmente, cmd3 se inicia cuando cmd2 ha terminado. Por el contrario, no hay ningún vínculo entre estos tres comandos; es decir, la ejecución de un comando no está condicionada por el resultado (código de retorno) del anterior.
En un script del shell, el punto y coma permite presentar varias instrucciones en una misma línea....
Variables especiales
Ciertas variables son definidas por el shell y pueden ser referenciadas por el usuario en los scripts del shell.
1. $$, $PPID
La variable especial $$ contiene el PID del shell que se está ejecutando, mientras que $PPID (Parent Process ID) da el PID de su proceso padre:
[javier]$ cat prog.sh
#!/bin/bash
echo "mi PID: $$"
echo "mi PPID: $PPID"
[javier]$ echo $$
1223
[javier]$ echo $PPID
1222
[javier]$ ./prog.sh
mi PID: 1380
mi PPID: 1223
[javier]$ . ./prog.sh
mi PID: 1223
mi PPID: 1222
2. $0
La variable $0 contiene el nombre del script en curso de ejecución tal como ha sido llamado en la línea de comandos:
[javier]$ cat prog.sh
#!/bin/bash
echo "mi nombre: $0"
[javier]$./prog.sh
mi nombre: ./prog.sh
[javier]$ /home/javier/prog.sh
mi nombre: /home/javier/prog.sh
3. $1, $2, $3, ...
Las variables $1, $2, $3, etc. representan los parámetros posicionales del shell, es decir, los argumentos pasados al script en la línea de comandos:
[javier]$ cat prog.sh
#!/bin/bash
echo "primer parámetro: $1"
echo "segundo parámetro: $2"
echo "tercer parámetro: $3"
[javier]$ ./prog.sh...
Comando test
El comando test permite efectuar una serie de pruebas sobre los archivos, las cadenas de caracteres, los valores aritméticos y el entorno de usuario.
Este comando tiene un código de retorno igual a cero cuando el test es positivo, y diferente de cero en caso contrario; esto permite utilizarlo en encadenamientos de comandos con ejecución condicional (&& y ||) o en las estructuras de control que veremos más adelante.
El comando test posee dos sintaxis: test expresión y [expresión], donde expresión representa el test que se debe efectuar.
La segunda sintaxis ofrece una lectura más fácil de las condiciones en las estructuras de control.
Los espacios detrás del corchete de apertura y antes del corchete de cierre son obligatorios en la sintaxis [expresión]. En general, todos los elementos de sintaxis del comando test deben ir separados por al menos un espacio.
El resto de la sección presenta los principales operadores que componen las expresiones de test del comando.
1. Test de archivos
-f archivo
Devuelve verdadero (código de retorno igual a cero) si el archivo es de tipo estándar (file):
[javier]$ test -f /etc/passwd
[javier]$ echo $?
0
[javier]$ [ -f /etc ] || echo "/etc no es un archivo estándar"
/etc no es un archivo estándar
-d archivo
Devuelve verdadero si el archivo es de tipo directorio (directory):
[javier]$ archivo='/etc'
[javier]$ [ -d "$archivo" ] && echo "$archivo es un directorio"
/etc es un directorio
Cuando se comprueba el contenido de una variable, es preferible encerrarla entre comillas para evitar un error en la sintaxis del comando test si la variable no está definida.
-r archivo
Devuelve verdadero si el usuario tiene permiso para leer el archivo (read).
-w archivo
Devuelve verdadero si el usuario tiene permiso para modificar el archivo...
Operaciones aritméticas
Como todo lenguaje de programación, el Bash ofrece las herramientas necesarias para el cálculo aritmético.
Para ello, existen principalmente los comandos expr, let y bc.
1. expr
expr es un antiguo comando externo del Bash y se presenta aquí sucintamente porque se prefiere el comando let que ofrece una sintaxis menos restrictiva.
Este comando devuelve en su salida estándar el resultado de las expresiones aritméticas pasadas como argumentos. Su sintaxis es expr expresión.
Todos los elementos de la expresión deben ir separados por al menos un espacio, y ciertos operadores aritméticos llevan como prefijo una barra invertida para evitar toda confusión con los caracteres especiales del shell.
Operadores aritméticos
Los operadores aritméticos son:
-
+: suma
-
-: resta
-
\*: multiplicación
-
/: división entera
-
%: resto de la división entera o módulo
-
\( y \): paréntesis
Se utiliza generalmente una sustitución de comandos para asignar el resultado del comando expr a una variable. Se obtiene por ejemplo:
[javier]$ expr 2 + 3
5
[javier]$ expr 2 - 3
-1
[javier]$ expr 2 + 3 \* 4
14
[javier]$ expr \( 2 + 3 \) \* 4
20
[javier]$ resultado=$(expr 9 / 2)
[javier]$ echo $resultado
4
[javier]$ expr $resultado % 3
1
Operadores lógicos
Los operadores lógicos son:
-
\|: o lógico
-
\&: y lógico
-
\<: estrictamente menor
-
\<=: menor o igual
-
\>: estrictamente...
Comando read
El comando read interrumpe la ejecución del shell hasta que el usuario introduzca una cadena de caracteres (aunque sea vacía) en su entrada estándar.
Las palabras que componen la cadena de caracteres escrita por el usuario se asignan a las variables cuyos nombres se pasan como argumentos al comando read:
[javier]$ read a b c
1 2 3
[javier]$ echo $a ; echo $b ; echo $c
1
2
3
Si hay más palabras que variables, la última variable contendrá el fin de la cadena de caracteres. En el caso inverso, las variables suplementarias serán nulas:
[javier]$ read a b c
1 2 3 4
[javier]$ echo $a ; echo $b ; echo $c
1
2
3 4
[javier]$ read a b c
1 2
[javier]$ echo $a ; echo $b ; echo $c
1
2
Si se llama al comando read sin argumentos, la respuesta del usuario se asigna a la variable de entorno $REPLY:
[javier]$ read
respuesta del usuario
[javier]$ echo $REPLY
respuesta del usuario
Se puede hacer que la entrada de una frase esté precedida por la opción -p (prompt) del comando read:
[javier]$ read -p "¿edad del capitán? " edad
¿edad del capitán? 12
[javier]$ echo $edad
12
Este comando se utiliza generalmente en los scripts interactivos; veamos un ejemplo de programa que pide la intervención del usuario:
[javier]$ cat prog.sh
#!/bin/bash ...
Estructuras de control
Las estructuras de control permiten ejecutar uno o más comandos según el resultado de una expresión.
La expresión proporcionada como condición de la estructura puede ser cualquier comando; el código de retorno de este comando es determinante. Se usan principalmente los comandos test o let como condiciones.
Sólo presentaremos las instrucciones if, for y while.
1. La instrucción if
La instrucción if ejecuta una serie de comandos si la condición indicada es verdad.
La sintaxis general es:
if condición
then
serie de comandos si condición verdadera
else
serie de comandos si condición falsa
fi
Cada palabra clave de la estructura (if, then, else y fi) debe encontrarse en una línea distinta; la cláusula else no es obligatoria.
Veamos un ejemplo de uso:
[javier]$ cat prog.sh
#!/bin/bash
if [ "$1" = "vale" ]
then
echo "está bien"
else
echo "no está bien"
fi
[javier]$ ./prog.sh vale
está bien
[javier]$ ./prog.sh novale
no está bien
2. La instrucción for
El bucle for ejecuta la misma serie de comandos tantas veces como valores...
Ejercicio
Ejercicio
Para cada extensión .conf, .cfg y .d, indique si hay más de 10 archivos o no, cuyo nombre termine con esta extensión en el directorio /etc.
Solución
La solución utiliza un bucle for para recorrer con la variable $ext las diferentes extensiones propuestas en el enunciado.
[javier]$ for ext in .conf .cfg .d
> do
> [[ $(ls -d /etc/*$ext | wc -l) -gt 10 ]] \
> && echo "hay más de 10 archivos que terminan con $ext" \
> || echo "hay menos de 10 archivos que terminan con $ext"
> done
hay más de 10 archivos que terminan con .conf
hay menos de 10 archivos que terminan con .cfg
hay más de 10 archivos que terminan con .d
El comando ls -d /etc/*$ext | wc -l, que devuelve el número de archivos correspondientes, es substituido ($(...)) por su resultado en el test ([[... -gt 10 ]]) que, determina si hay más de 10.
Si el resultado de test es verdadero (&&), ejecutamos el comando echo "...más..."; en caso contrario (||), ejecutamos el siguiente: echo "...menos...".