El uso de arrays puede resultar muy útil, en particular en scripts. Los arrays en bash tienen limitaciones (por ejemplo son de una sola dimensión, esto es, son vectores), pero también disponen de alguna caracteristica más avanzada, como los arrays asociativos. En cualquier caso, aquí solo se verán arrays indexados.
Hay varias formas de declarar un array. Aquí veremos tres de ellas, que son implícitas:
Para acceder a un elemento concreto de un array, se usa como como siempre $
precediendo la variable y el índice deseado entre corchetes, pero es obligatorio el uso de llaves de forma que se delimite sin ambigüedad que lo que se quiere es acceder a un elemento del array. Ej.:
Si se usa una variable para el índice, no es necesario usar $
(sí para referirse a los parámetros de entrada, por ejemplo $1, para que pueda diferenciar si se quiere acceder a la posición cuyo valor está en $1 o a la posición 1 del array). Por ejemplo:
Si se usa como índice *
o @
se accede a todos los elementos del array. Por ejemplo:
Es muy típico su uso en bucles for in
:
Sin embargo, *
y @
difieren cuando el array está entrecomillado (con @
diferencia cada elemento del array y con *
no):
La diferencia se aprecia todavía más si el array contiene elementos formados por más de una palabra. Por ejemplo:
Si retomamos el ejemplo de operaciones usando ahora un for in
, se hace necesario entrecomillarlo para que no ocurra la sustitución del tercer elemento del array (el *) por los ficheros del directorio actual. Pero para que cada elemento del array se trate individualmente en un entrecomillado, solo se puede utilizar @ como índice (con * se accedería a todo el array a la vez, que no es lo que se necesita):
Dado que como se ha visto los arrays pueden no tener definidos todos sus elementos, puede ser de utilidad saber cuáles existen. Para ello se usa la sintaxis ${!array[*]}
o ${!array[@]}
como se puede ver en el siguiente ejemplo:
En bash se puede utilizar ${#var}
para obtener el número de caracteres (longitud) de var
. Por ejemplo:
Una sintaxis parecida se utiliza para conocer el número de elementos de un array:
Se puede hacer especificando un elemento concreto, como por ejemplo:
O añadiendo al final, sin necesidad de especificar el índice:
Con esta última opción, podemos añadir varios elementos simultáneamente:
Como con otras variables, para eliminar un array se utiliza unset. Ej.:
También se puede usar para borrar elementos determinados por su índice, por ejemplo:
Es importante usar las comillas para que no se realicen expansiones de nombre de fichero a causa de los corchetes.
Las funciones facilitan la modularización del código. Generalmente disminuyen el tamaño de los programas ya que se escriben una sola vez pero pueden ser llamadas desde diversos puntos. Son útiles para la optimización, las pruebas y la reutilización del código. Además en muchos lenguajes (y la propia bash) permiten llamarse a sí mismas (funciones recursivas).
Una función se declara (formato POSIX) de la siguiente forma:
Una función se puede declarar directamente en la línea de órdenes:
Para llamar a una función, se escribe directamente su nombre (sin los paréntesis; estos se usan solo en la declaración):
El valor de $?
tras la ejecución de una función es, por defecto, el de la última instrucción ejecutada. Si se quiere que devuelva otro valor, se utiliza la instrucción return (que termina la función) acompañada del exit status que se desea.
Para ver las funciones que hay declaradas, se utiliza:
Como se puede ver, además de la función que hemos definido aparecen muchas otras. Estas funciones están declaradas en del fichero /usr/share/bash-completion/bash_completion
que se carga desde /etc/bash.bashrc
(archivo genérico de inicio de la bash para shell interactivos).
Para ver cómo está declarada una función se usa declare -f
o type
:
Una función definida se puede borrar con unset -f
:
En el resto de la sección dedicada a funciones nos centraremos en las definidas en scripts.
Sea el siguiente ejemplo:
Vamos a ejecutar el script:
Apoyándonos en el ejemplo, tenemos:
Ámbito de las variables:
$texto
sería accesible desde cadena).cadena
(nótese que se la llama siempre desde la función cabecera
), su variable $long
oculta a la variable $long
de la función cabecera
.Como se dijo, las funciones no devuelven realmente un valor con return , sino el exit status
Una forma de “retornar” valores, pero poco recomendada, es modificar variables globales o locales de la función llamante.
Una forma más recomendable es usar echo en la función llamada y “capturar” con $()
. Ej.:
$1
, $2
.$*
(todos los argumentos) y $#
(números de argumentos). Lo que no se altera es $0
que mantiene el valor original (nombre del script).command
para acceder a la orden original. Por ejemplo:Si tenemos funciones en un script (librería) que deseamos usar directamente desde la shell interactiva de la bash
se puede ejecutar el script con source
. Si queremos tenerlo siempre disponible, podemos añadirlo en ~/.bashrc
(para el usuario actual) o /etc/bash.bashrc
(para todos lo usuarios).
Se necesita diseñar un script que verifique el minado de un bloque de datos por parte de un tercero. El bloque consiste en una única línea que contiene tres campos separados por el carácter de dos puntos:
payload:nonce:hash
El primer campo payload
contiene la información del bloque, que puede ser una cadena de texto arbitraria. El campo nonce
es un contador que varía con objeto de hacer cambiar el resultado del hash
, y por último el campo hash
es el resultado de calcular el sha-512 de payload:nonce
(incluyendo el caracter de dos puntos :)
Por ejemplo, sea el siguiente bloque:
Hola caracola:1017:00e72927fc19dc3df6611252b6f27a779e42645f7360d033080b2402758864c731a21e8550e538c01bdeb09421fb0e1357e152ee58d932cdc5cfabccaececde6
Para verificar la prueba utilizaremos la orden shasum -a 512
(en algunas distribuciones de Linux, esta orden aparece con el nombre de sha512sum
) que recibe por stdin una cadena de texto a calcular. Obtenemos los dos primeros campos y los concatenamos con el carácter dos puntos: Hola caracola:1017
. A continuación calculamos el SHA-512 de esa cadena y obtenemos 00e72927fc19dc3df6611252b6f27a779e42645f7360d033080b2402758864c731a21e8550e538c01bdeb09421fb0e1357e152ee58d932cdc5cfabccaececde6
y finalmente comparamos si dicha cadena coincide con el tercer campo del bloque. De ser así, solo queda una prueba más para dar por válida la prueba: el hash generado debe comenzar por dos ceros.
Si se emplea echo para generar cadenas, no olvide usar el flag -n para evitar que echointroduzca un carácter de avance de línea al final de la cadena generada, ya que este es su comportamiento por defecto.
Realice un script bash que reciba como argumento un archivo que contenga el bloque a verificar y que muestre por pantalla si el archivo contiene una prueba correcta.
En nuestro sistema de criptomonedas, minar un bloque consiste en concatenar al bloque a minar (payload), usando como delimitador el carácter de dos puntos, un campo nonce con un contenido arbitrario, de forma que el hash SHA-512 de dicha concatenación comience por un determinado número de ceros. El número de ceros por el que debe comenzar el hash se conoce como grado de dificultad del minado y en nuestro ejemplo estará fijo a 2.
Realice un script que reciba como argumento un bloque (una cadena de texto arbitraria) y genere un bloque con el formato:
payload:nonce:hash
Tal que el SHA-512 de payload:nonce
sea igual a hash, y que dicho hash comience por la secuencia 00
.
Es habitual que en un sistema haya que realizar tareas que se repitan con cierta periodicidad, como hacer copias de seguridad todos los días, enviar un informe del uso de las impresoras semanalmente, etc. Para automatizarlas se suele utilizar cron. Dada la importancia que para el administrador del sistema tiene cron, se presenta aquí una pequeña introducción.
Cron es un demonio que se ejecuta cada minuto y comprueba si hay que realizar alguna tarea. Estas tareas vienen especificadas en ficheros crontab.
Si no se limita (para esto se utilizan los ficheros /etc/cron.deny
y /etc/cron.allow
[1]), cualquier usuario puede crear su propio fichero crontab para automatizar sus tareas repetitivas. Un usuario puede ver su fichero crontab utilizando crontab -l
. Si no existiera o lo deseara modificar, utilizaría la orden crontab -e
. Es importante utilizar este método ya que aunque los ficheros crontab son ficheros de texto cuyo nombre es igual el del usuario y que se encuentran en /var/spool/cron/crontabs
, si se modifican externamente el demonio (cron) no los relee y no se van a aplicar los cambios hasta que no sea reiniciado.
Supongamos que somos el usuario root y ejecutamos crontab -l
, que nos va a mostrar el contenido de nuestro fichero crontab, esto es, /var/spool/cron/crontabs/root
:
El significado de este fichero es el siguiente:
En primer lugar, las líneas en blanco y aquellas cuyo primer carácter no blanco es #
, que representan comentarios, se ignoran.
Por otro lado, en los ficheros crontab se pueden especificar ciertas variables de entorno propias de los crontab (aunque tengan nombres iguales a las de la shell), como la variable PATH
, que por defecto tiene el valor "/usr/bin:/bin"
. Otra variable de entorno de los crontab es SHELL
, que indica la shell que ejecutará las tareas (por defecto es /bin/sh
) y como se ve en el ejemplo se ha cambiado a /bin/bash
.
Por último, se encuentran las líneas que indican las tareas a ejecutar. Los cinco primeros campos indican el momento en que se ejecutarán, y el resto la orden a ejecutar (que en muchas ocasiones será un script). Cada línea va separada por una nueva línea, incluida la última. En este caso, hay planificadas dos tareas (una copia de seguridad y un borrado del temporal).
El significado de los cinco primeros campos (abreviados como m h dom mon dow) es:
m: minute (0-59)
h: hour (0-23)
dom: day of month (1-31)
mon: month (1-12)
dow: day of week (0-7; el domingo se representa como 0 y 7).
Cada uno de estos campos se puede especificar como rangos separando el valor inicial del final con un guión (por ejemplo 1-5
en el dow para especificar de lunes a viernes), como listas separando los valores por comas (por ejemplo, en el campo h 8-10,15,23
significa a las 8,9,10,15 y 23 horas) y todo el rango con *
(por ejemplo, en el campo m un *
significa lo mismo que 0-59). A su vez, en cada rango se puede especificar el "salto" entre valores añadiendo una barra (/
) seguida del valor del salto. Por ejemplo, si en el campo h se pone 8-15/2
es lo mismo que especificar las horas 8,10,12,14; y si en el campo mon se escribe */3
es lo mismo que especificar 1, 4, 7 y 10.
Cuando se especifica simultáneamente el día del mes y el día de la semana, se activa cuando se cumple cualquiera de ellos. Por ejemplo, si tenemos esta línea:
el comando x.sh se ejecutará a las 03:00 todos los meses los días 1 y los sábados.
Si la orden ejecutada generara alguna salida por la salida estándar, se redireccionaría automáticamente al correo del usuario.
Para más información: man crontab
, man 5 crontab
y man cron
.