Saltar al contenido principal

Scripting en Bash | Introducción

Documento escrito por: Alejandro Aranda

Puedes ver todos los documentos de este autor aquí.

Introducción

Hoy nos vamos a sumergir en el apasionante mundo del scripting, esta vez, en Bash. La idea es que este artículo sirva como una Introducción para cualquier persona que quiera aprender a hacer sus propios scripts en Bash.

Antes de empezar, es necesario que tengas conocimientos sobre Bash en general. Puedes consultar un artículo en el que ya hablé sobre este tema aqui.

Parámetros en bash

Cuando hablamos de parámetros en Bash, nos referimos a unas variables reservadas que utilizaremos principalmente para para recoger datos que introduzca el usuario.

./test.sh parámetro1 parámetro2 parámetro3

Tenemos varios tipos de parámetros en función de cómo recogen la información introducida a la hora de ejecutar el script:

Parámetros posicionales

Para llamar a los parámetros, pondremos un "$" y el número de parámetro, siendo el primer parámetro por ejemplo $1. Si el parámetro está en una posición cuyo número tiene más de una cifra, lo indicaremos de la siguiente manera: ${10}

He creado este pequeño programa para mostrar el funcionamiento de los parámetros.

Ejemploparámetro

Como se puede ver, utilizamos un espacio en blanco para separar cada parámetro.

Ejemploparámetro

En caso de querer pasar un parámetro que contenga varias palabras, utilizaremos comillas:

./test.sh "Buenos días a todos" parámetro3 parámetro4

De esta forma, el texto Buenos días a todos sería el primer parámetro.

Devolver todos los parámetros

Ya hemos visto como trabajar con los parámetros uno por uno, pero sería normal pensar, ¿Hay alguna forma de trabajar con todos de forma directa?

La respuesta es un rotundo si, en Bash contamos con dos expresiones para referirnos a todo el conjunto de parámetros, $* y $@.

He creado este pequeño programa para mostrar cómo funcionan estas expresiones.

Ejemploparámetro

Como vemos, nos devuelven en la misma fila todos los parámetros.

Ejemploparámetro

Aunque pueda parecerlo a primera vista, estas dos expresiones no funcionan de la misma forma. Podemos apreciar las diferencias cuando utilizamos bucles (que explicaré más adelante).

Ejemploparámetro

En este programa, iteraré con las variables uno y dos sobre cada parámetro. Lo realmente importante en este ejemplo es que, cuando utilizamos "$@" con comillas Bash interpreta dos palabras separadas como si fuese un solo parámetro.

Ejemploparámetro

Contar número de parámetros

Algo muy útil cuando creamos scripts en Bash es saber cuántos parámetros ha introducido el usuario. Esto lo podemos hacer utilizando la expresión $#

Ejemploparámetro

Vemos que es capaz de interpretar como un solo parámetro dos palabras que cuentan con espacios entre sí.

Ejemploparámetro

Más adelante cuando veamos estructuras de control, veremos que esto es muy útil para comprobar si el usuario ha introducido parámetros y, por ejemplo, en caso de no haberlo hecho, no ejecutar el programa.

Variables en Bash

Una variable es un elemento capaz de almacenar datos para posteriormente recuperarlos para su uso. Esto nos será muy útil para hacer operaciones aritméticas, comparaciones y trabajar con los datos en general.

Una característica muy importante de las variables es que pueden tomar diferentes valores durante toda la ejecución del programa.

Variables comunes

Cuando hablo de este tipo de variables me refiero a aquellas que son definidas por el usuario, ya sea en el mismo script o recogidas de alguna forma.

Variables alfanuméricas

El ejemplo más simple del funcionamiento de una variable lo observamos directamente en la terminal:

Ejemploparámetro

Definiremos una variable escribiendo su nombre y recuperaremos su valor utilizando el símbolo $.

Ejemploparámetro

Como vemos, una vez definidas las variables, podemos trabajar con ellas sin ninguna limitación.

danger

Cuando definimos variables debemos tener en cuenta los espacios.

Ejemploparámetro

En caso de querer definir una variable que contenga espacios en blanco, debemos utilizar comillas dobles.

Ejemploparámetro

Recoger un valor por teclado

En Bash, tenemos la opción de pedirle al usuario que ejecuta el script que introduzca valores por teclado, algo realmente útil. Esto lo haremos de la siguiente forma:

Ejemploparámetro

Con read le pedimos al usuario introducir datos por teclado.

Ejemploparámetro

Variables con operaciones aritméticas

También podemos crear variables que almacenen el resultado de operaciones aritméticas. Esto es muy útil por ejemplo en un bucle en el que queremos saber cuántas iteraciones se han realizado.

Indicamos que es una operación que debe ser interpretada utilizando $((..))

Ejemploparámetro

Esto combinado con lo visto anteriormente, nos permite crear por ejemplo un programa que opere dos números.

Ejemploparámetro

Definimos cada variable que contendrá el valor de la operación que corresponda y luego mostramos su valor.

Ejemploparámetro

Variables asignadas con comandos

Puede sonar raro, pero Bash nos permite que una variable contenga como valor el resultado de un comando. Aunque pueda no parecerlo, esto nos permite hacer cosas realmente interesantes.

Para guardar el resultado de un comando en una variable lo hacemos con $(comando)

Guardar en una variable un listado ficheros del directorio actual:

Ejemploparámetro

Podríamos por ejemplo con esta línea contar el número de ficheros que hay en el directorio actual.

find . | wc -l

Ahora lo metemos en una variable y podemos trabajar con ello:

Ejemploparámetro

Variables especiales

En Bash, hay algunas variables especiales que se refieren a información relacionada con el script. En este grupo de variables estarían también las vistas anteriormente en el apartado de parámetros.

$USER el nombre del usuario que ha ejecutado el script

$HOSTNAME se refiere al nombre de la máquina en la que se está ejecutando el script

$SECONDS se refiere al tiempo (en segundos) transcurrido desde que se inició el script.

$RANDOM devuelve un número aleatorio cada vez que se lee esta variable.

$LINENO indica el número de líneas que tiene nuestro script.

Veamos un ejemplo:

Ejemploparámetro

Ejemploparámetro

Variables de entorno

Las variables de entorno son cadenas que contienen información acerca del entorno para el sistema y el usuario que ha iniciado sesión en ese momento.

Esto nos puede ser muy útil ya que nos da información dinámica, es decir, que va cambiando en función del sistema y el usuario.

Principales variables de entorno:

$LOGNAME Contiene el nombre del usuario con el que estas logeado.

$SHELL Contiene el tipo de terminal que utiliza el usuario.

$PWD Su valor es la ruta en la que se encuentra el usuario.

$HOME Su valor es el directorio personal del usuario.

$LANGUAGE El idioma con el que va a trabajar el SO.

Un ejemplo:

Ejemploparámetro

Ejemploparámetro

info

Podemos consultar las variables de entorno escribiendo env en la terminal

Operadores aritméticos

Ahora que ya sabemos trabajar con variables, estaría bien conocer los operadores aritméticos que Bash pone a nuestra disposición. Estos operadores serán muy útiles cuando estudiemos las estructuras de control.

OperadorDescripciónEjemplo
+Suma dos númerosEjemploparámetro
-Resta el segundo al primeroEjemploparámetro
*Multiplica dos númerosEjemploparámetro
/Devuelve el cociente de dividir el primero entre el segundoEjemploparámetro
%Devuelve el resto de dividir el primero entre el segundoEjemploparámetro
+=Incrementa el valor de la variable en 1Ejemploparámetro
-=Decrementa el valor de la variable en 1Ejemploparámetro
*=Multiplica el valor de la variable por el valor dadoEjemploparámetro
/=Divide el valor de la variable entre el valor dadoEjemploparámetro
%=Divide el valor de la variable entre el valor dado y devuelve el restoEjemploparámetro
**Eleva un número a otro valor dadoEjemploparámetro
info

En algunos casos, cuando creamos ficheros no es necesario utilizar la expresión $((..)) para realizar las operaciones

Operadores lógicos

En Bash, una forma de validar si un comando ha sido exitoso o no, es usando operadores lógicos. Los principales son los siguientes:

Operador AND

Este operador devuelve un valor "true" si la operación anterior se ha completado, y si esto sucede, la siguiente operación se realizará.

En caso de que la operación anterior no se realice de forma correcta, la siguiente operación no se realizará.

Por ejemplo, podríamos crear una expresión para comprobar la conexión con los servidores DNS de Google 8.8.8.8 y en caso de tener conexión, que nos muestre un mensaje.

timeout 1 ping -c 1 8.8.8.8 && echo Correcto

El comando "timeout", lo utilizamos para prevenir que el comando "ping" se quede colgado por no tener conectividad.

EjemploAND

Redirigimos todo al /dev/null con el carácter ">" para que no nos muestre por pantalla el resultado del primer comando.

El /dev/null es algo así como un agujero negro en el que redireccionar información que será descartada.

Otro ejemplo:

En este caso, utilizamos el operador AND para que, después de actualizar los repositorios exitosamente nos muestre un mensaje informativo por pantalla.

sudo apt update -y >/dev/null && echo Actualizado correctamente

En caso de no completarse exitosamente el comando, no mostraría nada por pantalla.

EjemploAND

Operador OR

Este operador es prácticamente opuesto al anterior. Es decir, la segunda operación se realizará si la primera no es exitosa.

Podemos usar los ejemplos anteriores para ver las diferencias.

timeout 1 ping -c 1 8.8.8.8 || echo Comando anterior no completado

En este caso cuando el comando ping no sea exitoso, nos mostrará el mensaje.

EjemploAND

Estos operadores pueden ser realmente útiles para operaciones sencillas si los usamos en conjunto.

Ejemplos con AND y OR

1. Comprobar si un usuario existe e informar por pantalla

Podríamos comprobar fácilmente si un usuario existe y mostrar un mensaje informativo por pantalla.

grep usuario /etc/passwd >/dev/null && echo Existe || echo No existe

En este caso filtramos con el comando grep por el usuario alex en el fichero /etc/passwd que contiene un listado de los usuarios del sistema.

EjemploAND

Como se puede ver, en función de si el usuario existe o no, veremos un mensaje por pantalla que nos lo indique.

2. Comprobar si un usuario existe e informar por pantalla

Otra cosa que podemos hacer usando estos operadores lógicos es comprobar si un fichero existe o no.

test -e fichero && echo El fichero existe || echo El fichero no existe

EjemploAND

Comparaciones

Cuando vayas a hacer comparaciones te recomiendo que utilices siempre [[..]] para evitar errores.

info

Podemos negar una expresión utilizando !. Por ejemplo != significaría que no sea igual y ! -f comprueba que no exista el fichero.

Comparando cadenas

A la hora de comparar cadenas debemos recordar utilizar comillas dobles ".." para evitar errores. Puedes consultar ejemplos más abajo.

ExpresiónSignificado
<Menor que
>Mayor que
=Igual a
!=Distinto de
-zCadena vacía
-nCadena no vacía

Comparando enteros

ExpresiónSignificado
-ltMenor que
-gtMayor que
-geMayor o igual
-leMenor o igual
-eqIgual a
-neDistinto de

Comparando atributos de ficheros

ExpresiónSignificado
-eEl fichero o directorio existe
-dEl directorio existe
-fEl fichero existe
-sEl archivo no está vacío
-rTiene permiso de lectura
-wTiene permiso de escritura
-xTiene permiso de ejecución

Condicionales con if / case

Una instrucción condicional es aquella que establece qué instrucciones deben de ejecutarse o no, en función del valor de una condición. Puede sonar complicado pero es realmente muy intuitivo, por ejemplo: Si llueve, no saldré a la calle.

Es decir, plantearemos una "prueba lógica" y en función de si se cumple o no, haremos una cosa u otra. Puedes ver ejemplos aquí

Utilizando if

Es la forma más habitual a la hora de comprobar si cierta condición se cumple. Ejecuta un bloque de código si se cumple la condición y otro bloque si no se cumple:

Esta es la forma más simple, ten en cuenta que el contenido ente [] es opcional.

if "condicion"          
then
"comandos"
[else
"comandos"]
fi

Otra forma de hacerlo sería proponiendo ciertas condiciones que se deben cumplir, esto lo haremos con elif, que nos permite comprobar de nuevo:

if "condicion"
then
"comandos"

elif "condicion"
then
"comandos"

[else
"comandos"]
fi

Utilizando case

Otra forma de comprobar si una condición se cumple, es utilizando case. La mayor diferencia que tiene frente a if es que nos permite definir muchos casos de forma sencilla y con menos lineas de código.

Usando case, la expresión *) actúa de forma parecida a else, es decir si no se cumple ninguna de las condiciones anteriores, se ejecutará el código de *).

case expresión in
caso_1)
comandos;;
caso_2)
comandos;;
*)
comandos;;
esac

Ejemplos de uso

Veamos unos cuantos ejemplos de uso:

1. Ejemplo básico if / elif

Como se puede ver en las expresiones, haremos diferentes cosas en función del número introducido como primer parámetro.

EjCondicional

EjCondicional

2. Ejemplo con case

En este caso, para mostrar el funcionamiento de case definimos un programa que creará un fichero o un directorio en función del primer parámetro introducido por el usuario.

En caso de no introducir un parámetro contemplado en el código, *) le mostraremos un mensaje que se lo indique.

EjCondicional

EjCondicional

3. Ejemplo con if y case

Le he dado una vuelta de tuerca al ejemplo anterior para comprobar con if que se introducen exactamente dos parámetros.

La primera comparación if [[ $# -ne 2 ]] hace que el programa devuelva un mensaje de error en caso de no introducir dos parámetros.

EjCondicional

EjCondicional

Bucles con for / while

Los bucles son realmente útiles en programación ya que nos permiten ejecutar muchas veces la misma secuencia de código hasta que la condición dada deja de cumplirse.

Puedes ver ejemplos aquí

Utilizando for

Un bucle FOR nos permite repetir instrucciones un número fijo de veces que indicamos con una serie de condiciones sobre una variable iteradora (que contendrá el valor en cada ciclo).

La estructura básica de for es esta:

#!/bin/bash
for nombre [in lista]
do
comandos que pueden utilizar $nombre
done

Encontramos varias formas de usarlo:

En este caso, vamos a iterar con la variable p sobre cada elemento de la lista, y en cada ciclo vamos a mostrar con echo el nombre de la página.

#!/bin/bash
for p in www.zonabit.es www.google.es
do # El output sería:
echo "Pagina: $i" # Primer ciclo -> Pagina: www.zonabit.es
done # Segundo ciclo -> Pagina: www.google.es

Otra forma de usarlo es dándole un rango de números (en este caso del 1 al 5). De esta forma logramos que el bucle se ejecute el número de veces que nosotros queremos.

#!/bin/bash
for i in {1..5}
do
echo "Numero: $i" # En cada ciclo, el script mostraría el numero por el que va
done

Por último, vemos una forma un poco más complicada pero también más versátil. En este caso tenemos tres elementos.

  • c=1 Inicializamos la variable c con el valor 1.

  • c<=5 Definimos que el bucle se ejecutará mientras c sea menor o igual a 5.

  • c++ En cada iteración la variable c aumentará su valor en 1.

#!/bin/bash
for (( c=1; c<=5; c++ ))
do
echo "Numero: $c" # La salida de este comando será exactamente la misma al ejemplo anterior
done

Utilizando while

Un bucle WHILE nos permite repetir instrucciones mientras una condición sea verdadera, es muy útil cuando no se sabe cuántas veces habrá que repetir una tarea hasta que consigamos lo que necesitamos.

while [ condición ]
do
// Código a ejecutar
done

Algo muy útil que podemos hacer es leer lineas de un fichero (ver ejemplo más adelante).

while read line;
do
// Código a ejecutar
done < fichero

Ejemplos de uso

1. Ejemplo con for

En este ejemplo podemos ver como utilizando for iteramos sobre $@ que como ya sabemos es un array de todos los parámetros introducidos.

Además, para poder contar el parámetro en cuestión, utilizo la variable par que se irá incrementando en 1 en cada iteración del bucle.

EjemploBucle

EjemploBucle

2. Ejemplo con while

En este caso vemos cómo utilizar un bucle while para leer un fichero línea a línea. La variable line será la que almacena cada línea en el bucle.

Igual que antes, cuento con una variable num que llevará la cuenta de la línea en la que estamos.

EjemploBucle

EjemploBucle

3. Ejemplo con for y while

Para este ejemplo utilizaré ambos bucles. El bucle for servirá para iterar por cada parámetro introducido por el usuario (como en el primer ejemplo).

En cada iteración, el bucle while leerá línea a línea cada fichero y mostrará su contenido por pantalla.

EjemploBucle

EjemploBucle

Funciones en Bash

Una función Bash es un bloque de código que agruparemos bajo un nombre, cada vez que queramos ejecutar ese bloque de código, simplemente tenemos que llamar a la función.

El propósito de una función es ayudarnos a hacer que nuestros scripts sean más legibles, y evitar escribir el mismo código una y otra vez.

Declarar una función

Para declarar una función, lo haremos de la siguiente manera:

 nombre_funcion () {comandos_a_ejecutar}

Veamos un ejemplo:

Empezaremos escribiendo el nombre de la función hola_zonabit () { tras esa línea, escribimos los comandos que queremos ejecutar y cerramos la llave }

Para llamar a la función simplemente escribimos el nombre de esta.

# Declaramos la función
hola_zonabit () {
echo 'Bienvenid@ a Zonabit' # Comando que se ejecutará cuando llamemos a la función
}

hola_mundo # Llamada a la función

Alcance de las Variables

Antes de definir variables en nuestras funciones debemos conocer los tipos de variables que encontramos en Bash.

  • Variables globales

Son variables a las que se puede acceder desde cualquier parte del script. En Bash, todas las variables por defecto se definen como globales, incluso si se declaran dentro de la función.

  • Variables locales

Las variables locales se pueden declarar dentro del cuerpo de la función con la palabra clave local y se pueden usar solo dentro de esa función.

Tenemos el siguiente script:

EjemploFuncion

Al ejecutarlo podemos ver como la variable local (definida en la función) no ha afectado a la variable global var1 definida anteriormente.

Esto sucede ya que las variables locales solo toman un valor mientras se ejecuta la función.

EjemploFuncion

Probaremos ahora a sacar el valor de la variable var1 dentro de la función.

EjemploFuncion

Veamos la salida:

EjemploFuncion

Como vemos, mientras se ejecuta la función, la variable var1 toma el valor que hemos definido en la función, es decir "1".

Parámetros en funciones

Antes vimos como a un programa en Bash le podemos pasar datos utilizando parámetros, de la misma forma podemos hacerlo con funciones.

EjemploFuncion

Como vemos, la función se ejecuta con los valores que le hemos pasado por argumento en cada caso.

EjemploFuncion

Ejemplo de uso

Este pequeño programa hará operaciones matemáticas con los parámetros introducidos por el usuario a la hora de ejecutar el script.

En este caso, llamamos a la función con los parámetros $1 y $2 ya que queremos que nuestra función haga operaciones con los datos introducidos por el usuario a la hora de ejecutar el script.

EjemploFuncion

Veamos el resultado:

EjemploFuncion

Como podemos ver, las funciones nos permiten tener una estructura más sana en nuestro código, ya que todo estará organizado en bloques.