Bash (Bourne Again Shell) es el intérprete de comandos más ampliamente difundido para la serie de sistemas operativos GNU y muchos otros sistemas tipo UNIX, deriva de Korn Shel y se ha convertido en un estándar de facto, incorporando la experiencia acumulada durante décadas por numerosos intérpretes desarrollados para sistemas tipo UNIX. A diferencia de los intérpretes de comandos de otros sistemas operativos de identidad fuertemente visual, donde el intérprete de comandos fue desarrollado como un componente necesario pero indeseable del sistema, el intérprete de comandos Bash es una herramienta poderosa, pensada para un uso intensivo en la administración y operación habitual de los sistemas operativos tipo UNIX, incluyendo GNU/Linux.
El diseño de Bash responde a una filosofía común de UNIX de dividir las soluciones complejas en subtareas simples y breves, encadenándolas luego en una combinación que resuelva el problema más complejo a partir de soluciones elementales. Siguiendo este criterio, Bash incorpora un lenguaje de programación de scripts (pequeños programas interpretados sin necesidad de compilación previa) que es a la vez conceptualmente básico y funcionalmente poderoso.
Todo usuario del sistema operativo GNU utiliza scripts de Bash aún cuando puede no saberlo, ya que gran parte del sistema operativo, comenzando por los scripts que inicializan los servicios del sistema durante el arranque, están escritos utilizando el scripts de Bash. Y es frecuente también que incluso usuarios no técnicos aprendan a programar pequeños scripts de Bash para automatizar tareas frecuentes que involucran múltiples comandos sencillos.
El objetivo de este artículo es ejemplificar, a partir de una particularidad del lenguaje de programación de scripts de Bash, la utilidad y potencia de los scripts. No vamos a enseñar a programar scripts de Bash, sino a demostrar su utilidad a partir del análisis de una característica particular del lenguaje que frecuentemente se desconoce o es infrautilizada. Estamos hablando de la "indirección" o "referencia indirecta" de variables.
De modo que si no ha programado antes para Bash, puede seguir leyendo este artículo para conocer una manera de trabajar con Bash aunque no conozca completamente los elementos utilizados, seguramente comprenderá la idea general del artículo, y puede visitar los siguientes sitios de referencia para aprender desde el comienzo los elementos básicos de Bash y comenzar a trabajar con él. Estas son guías de Bash disponibles en forma libre y gratuita en Internet:
http://linux-cd.com.ar/manuales/howtos/programacion-bash/Bash-Prog-Intro-COMO.html
http://www.tldp.net/LDP/Bash-Beginners-Guide/html/index.html
Indirección de variables
La indirección o referencia indirecta es un técnica de programación que permite utilizar una variable para hacer referencia a otra variable distinta, que a su vez contiene un dato de interés. Es decir que la primer variable no contiene el dato en sí, sino que contiene una referencia a otra variable que sí contiene un dato concreto. La mayoría de los lenguajes de programación permiten la indirección de variables, y aunque la implementación de esta funcionalidad es diferente en distintos lenguajes, en última instancia siempre el mecanismo subyacente consiste en que la primer variable contiene en su espacio de memoria la dirección de memoria en la cuál se aloja la segunda variable, y la segunda variable contiene en su espacio de memoria un dato más concreto, el valor final que interesa al usuario. Por ejemplo:
A y B son dos variables. La variable A está alojada en la dirección de memoria 0x00000200 La variable A contiene el siguiente dato, un nombre: "UTUTO XS" La variable B está alojada en la dirección de memoria 0x00005000 La variable B contiene el siguiente dato, la dirección de A: "0x00000200"
Es decir, B contiene una referencia a la ubicación en la memoria donde puede encontrarse el contenido de A. Cada lenguaje que implementa indirección tiene algún mecanismo o expersión que permite obtener el valor de la variable directa, que en el caso de B sería 0x00000200, o el valor de la variable indirecta, que en caso de B sería "UTUTO XS", el valor de la variable referenciada A. De este modo, el nombre "UTUTO XS" es posible recuperarlo tanto a partir de la variable A (en forma directa) como de la variable B (en forma indirecta)
Por supuesto, podríamos plantear otro hipotético caso en el cuál una tercer variable C contiene el valor 0x00005000, una referencia a la ubicación de la variable B. Múltiples nivele de indireccion son posibles en la mayoría de los lenguajes que implementan esta característica.
Indirección en Bash
En realidad, el tipo de implementación de indirección de variables que acabamos de mencionar es el más simple y es el que utilizan los lenguajes que manejan punteros, como es el caso del lenguaje C. En el caso de Bash, la variable indirecta contiene el nombre, y no la dirección, de la variable referenciada. Tomando del ejemplo anterior, el contenido de B no sería "0x00000200" sino "A". El intérprete Bash implementa la funcionalidad necesaria para referenciar al contenido de la variable A a partir del nombre "A" almacenado en la variable B.
Nuestro primer ejemplo de indirección en Bash es el siguiente:
#!/bin/bash A="UTUTO XS" B="A" echo "\$A: $A" # Salida: $A: UTUTO XS echo "\$B: $B" # Salida: $B: A echo "\${!B}: ${!B}" # Salida: ${!B}: UTUTO XS
Desde la versión 2 de Bash, existen dos maneras de referenciar indirectamente una variable. Anteriormente la referencia indirecta se hacía con la ayuda del comando eval, de este modo:
#!/bin/bash A="UTUTO XS" B="A" eval C=\$$B echo $C # Salida: UTUTO XS
A partir de la versión 2 de Bash, la indirección puede expresarse mas intuitivamente como se muestra en el primer ejemplo, utilizando llaves y un signo de admiración: ${!B}.
Utilización de la indirección en Bash
Demostraremos entonces algunos modos de aplicación útil de la indirección de variables de Bash. La principal ventaja que puede obtenerse del uso de indirección de variables es la posibilidad de programar elegantemente scripts que respondan a parámetros variables o configuraciones flexibles, es decir que se puede programar funciones más genéricas, reutilizando código más fácil, más útil y más eficiente.
Indirección y parámetros
Un caso muy simple responde a cómo obtener el valor del último parámetro recibido por un script, cuando la cantidad de parámetros totales varía o es desconocida. Para obtener el último parámetro pasado a un script, independientemente de la cantidad de parametros que se hayan recibido:
#!/bin/bash parametros=$# # Número de parámetros echo ${!parametros} # El último parámetro referenciado por número # Es una alternativa a ${!#}
Imaginemos un script que debe operar en relación a una variable, pero de antemano se desconoce cuál sea esa variable, quizá debe ser leída desde algún archivo o es una variable declarada en el programa o una variable de entorno. En el siguiente ejemplo un archivo es editado por los usuarios, incorporando información, y luego otro usuario solicita se ejecute una operación sobre cualquiera de los datos incorporados al archivo.
#!/bin/bash ######### # Este listado podriá ser incluido (source) # desde otro archivo de configuración rotores="AR12 AR15 AR21" soportes="SO11 SO12 SO13 SO15" motores="RX06 RX09 RX15" ######### function ejecutarInforme() { for codigo in ${!1} do echo "Generando informe $codigo" if [ -f informe_$codigo.sh ]; then sh informe_$codigo.sh else echo "El informe $codigo aún no está disponible" fi done } ejecutarInforme $1 # Ejecutar con parámetro "rotores" o "soporte" o "motores"
Variables con nombres compuestos
Una característica de Bash es la posiblidad de utilizar una variable para conformar parte del nombre de otra variable, y luego acceder tanto al nombre como al valor de la variable mediante el uso del mecanismo de indirección, por ejemplo así:
#!/bin/bash variable_uno="Yo soy 1" variable_dos="Yo soy 2" for orden in uno dos do variable="variable_$orden" echo $variable = ${!variable} done # Salida: # variable_uno = Yo soy 1 # variable_dos = Yo soy 2
Este es uno de los ejemplos más simples y claros de la indirección de variables.
Arrays + indirección = magia!!
La combinación de arrays e indirección de variables demuestra toda la potencia del método. Es especialmente útil para crear archivos de configuración coherentes, fáciles de modificar, potentes y flexibles, pero sobre todo, que no necesitan ser parseados, el mismo Bash se encarga de eso en forma trasparente, por lo tanto la probabilidad de bugs disminuye notoriamente. El siguiente ejemplo demuestra la configuración de un pequeño programa utilizando arrays e indirección de variables:
#!/bin/bash # Archivo de configuración: pruebaservidores.conf servidores=( srvmysql srvapache ) srvmysql_config=( nombre=SrvMySQL ip=172.16.2.1 ) srvapache_config=( nombre=SrvApache ip=172.16.2.4 )
#!/bin/bash # Programa principal: pruebaservidores.sh source pruebaservidores.conf function probarServidor() { # Esta es la función que hace la "magia" # usando indirección de variables # Crea una variable local con el nombre del array # que contiene la configuración del servidor local servidor="$1[*]" # Itera cada elemento de la configuración # creando variables locales, usando indirección for config in ${!servidor} do # Crea instancias locales a partir de los elemntos # del array que configura cada servodr local $config done # Ahora cada elemento del array es una variable usable localmente echo $nombre $(ping -c 5 $ip | grep -o "[0-9]*% packet loss") } function probarTodo() { local servidor for servidor in ${servidores[*]} do local srvconfig="${servidor}_config" probarServidor $srvconfig unset srvconfig done } probarTodo
Como se puede apreciar, el programa tiene un archivo de configuración que casi no requiere ningun esfuerzo de programación adicional para leer la configuración del usaurio, y éste a su vez encuentra una estructura de configuración sumamente intuitiva y fácil de adaptar a sus necesidades particulares, pudiendo incluso incorporar lógica de proceso en caso de ser un usuario técnico y tener un buen motivo para hacerlo.
Un sistema real
Para completar los ejemplos con un caso real de implementacion de indirección de variables Bash, les recomiendo descargar e investigar un sencillo sistema de backups desarrollado para una entidad gubernamental, desarrollado enteramente en Bash y haciendo uso intensivo de indirección de variables. El sistema es Libre, licenciado bajo GNU GPLv3, y puede descargarse desde la siguiente URL:
http://pablorizzo.com/archivos/resguardos.7z
Conclusión
Puede que el método de indirección o referencia indirecta de variables no sea totalmente evidente e intuitivo a primera vista, tampoco tiene la potencia y sencillez de los punteros en los lenguajes compilados como C, pero con paciencia y un poco de estudio se descubren como una característica que le cambia la cara al lenguaje de scripts de Bash, añadiendo gran flexibilidad a la ya conocida potencia de Bash como herramienta de administración del sistema operativo GNU.
— Copyright (c) Pablo Manuel Rizzo Permission is granted to copy, distribute and/or modify this document under the terms of the GNU Free Documentation License, Version 1.2 or any later version published by the Free Software Foundation; with no Invariant Sections, no Front-Cover Texts, and one Back-Cover Text: "El autor del texto original es Pablo Manuel Rizzo, puede ser contactado directamente al email info@pablorizzo.com" A copy of the license is included in the section entitled "GNU Free Documentation License".