Cerramos del 10/08 al 18/08. Los pedidos cursados a partir del 09/08 a las 12.00 hora de España peninsular se tramitarán el 19/08/2024.
Cerramos del 10/08 al 18/08. Los pedidos cursados a partir del 09/08 a las 12.00 hora de España peninsular se tramitarán el 19/08/2024.
  1. Libros
  2. Linux
  3. Programación y scripts Bash
Extrait - Linux Principios básicos de uso del sistema (8ª edición)
Extractos del libro
Linux Principios básicos de uso del sistema (8ª edición)
2 opiniones
Volver a la página de compra del libro

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í:

images/1001scr1.png

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...".