Publi

Leer ficheros de configuración INI desde nuestros scripts en BASH

Archivos de configuración en BASH
Una de las tareas que debemos hacer como programadores es la de facilitar al usuario la configuración de nuestros programas. Haciéndolos más flexibles y adaptables a las necesidades de cada individuo.
Y una forma muy fácil de definir configuración para nuestros programas es en ficheros con formato INI. Este formato se introdujo en los años 90 en versiones de Windows como la 3.1. En aquella época, teníamos en varios archivos con extensión INI la configuración de nuestro sistema Windows y teníamos que modificar los archivos y reiniciar Windows para que los cambios tuvieran efecto. Windows, por aquel entonces era un programa más que se ejecutaba en el ordenador, que trabajaba con MS-DOS.

¿Qué tienen esos archivos?

El contenido de los archivos es sencillo. No es más que un montón de líneas con claves y valores, con esta forma:

clave=valor

Es decir, la clave será una palabra (preferiblemente sin espacios ni símbolos, sólo letras y un guión bajo, teóricamente no debe contener ni punto y coma (;) ni almohadilla (#) porque están reservados para los comentarios; ni corchetes ([]) porque son parte de las secciones.
Eso sí, a partir del primer igual, hasta el final de la línea cualquier carácter formará parte del valor. Ya puede ser un número, letra, punto y coma, corchete.
Además, las claves pueden estar encerradas en una categoría o sección, para que sea sencillo, tanto para usuarios como programadores trabajar con muchas claves. Las secciones vendrán especificadas de la siguiente forma:

[seccion]

Estos ficheros también podrán contener comentarios, como dije antes, con ; o #. Y se usan por muchos programas para configurar ciertas partes o componentes de los mismos porque son archivos muy fáciles de leer por una máquina y muy fáciles de editar por un ser humano. No son perfectos, pero en muchos casos es lo único que necesitamos.

¿Por qué en BASH?

Normalmente las personas que trabajamos con scripts para Bash tenemos muchas formas de incluir configuración de nuestros scripts:

  • Dentro del propio script. Dentro del mismo script puedes incluir algunas líneas al principio con algunas definiciones. En muchos casos está muy bien.
  • En otro script. En Bash, podemos poner un punto, espacio y el nombre del fichero que queremos incluir. De esa forma, las variables declaradas en ese otro fichero serán visibles en el actual.
  • En un archivo no ejecutable. Lo malo de incluir archivos de Bash es que pueden contener código ejecutable. Y no deberíamos dejar que un usuario pueda ejecutar código donde no debe. Así que una buena opción es la de crear un archivo que debamos leer y parsear. Ya sea un archivo XML, JSON, YAML o INI. Tal vez sea la opción más lenta y más larga, pero será la más segura.

Ahora bien, Bash, por su forma de trabajar, presenta varios problemas en este aspecto. En el transcurso de nuestro programa deberemos obtener los valores para varias claves y, Bash se caracteriza por ejecutar todos los programas a los que llamamos de forma secuencial. Es decir, aunque está muy optimizado, si tenemos que llamar repetidas veces a sed, awk, grep, cut o cualquier otro programa, el tiempo de ejecución se va a resentir. Cada vez hay más órdenes nativas de Bash que nos evitan tener que cargar un programa nuevo en memoria y su consiguiente ejecución, destrucción, cambios de contexto y demás cosas que hacen los sistemas operativos modernos. Algunos ejemplos los podemos encontrar en este post: manejo de cadenas en Bash.

Múltiples opciones

En este post voy a poner varias formas de hacer las cosas. Nuestra gran responsabilidad será utilizar la que creamos conveniente en cada momento. Depende de nuestras necesidades en cada momento. Por ejemplo, si vamos a leer solo dos líneas de configuración, y no necesitamos secciones ni nada, podríamos utilizar una forma que es muy corta, y un poco lenta (total, para dos lecturas tampoco vamos a perder una eternidad). Pero por ejemplo, si nuestro fichero de configuración tiene 100 líneas, secciones y algunas partes inseguras (pedazo de script), seguro que nos conviene más utilizar un parseo del fichero de configuración más rápido y fijarnos un poco en la seguridad del sistema.

Todo esto lo iré explicando detalladamente.

Evaluando el código en Bash

Esto al final es como si incluimos el fichero en Bash, pero hacemos una pequeña transformación para que a Bash le guste un poco lo que le vamos a meter. Personalmente no me gusta esta opción porque no soluciona muchos problemas, nos permite ejecutar código desde el fichero de configuración, nos permite sobreescribir variables que ya tengamos en el código y algunas cosas más que lo hacen tremendamente inseguro, aunque es muy rápida.

Imaginemos que tenemos un fichero ini sencillo como este (simple.ini):

1
2
3
4
servidor=db.dominio.com
puerto=3306
usuario=armandoguerra
password=arreugodnamra32

Ahora, en nuestro código podemos hacer esto:

1
2
3
source <(grep servidor simple.ini)

echo "Servidor: "$servidor""

Con este código capturaríamos la variable servidor dentro del fichero ini. En realidad, hacemos que se evalúe el contenido del fichero ini como si fuera de Bash. Si queremos evaluar el fichero completo para extraer todos los elementos podríamos hacer esto:

1
2
3
4
5
6
source <(grep = simple.ini | sed -e 's/\s*=\s*/=/g' -e 's/^;/#/g')

echo "Servidor: "$servidor""
echo "Puerto: "$puerto""
echo "Usuario: "$usuario""
echo "Password: "$password""

Aquí extraemos todas las líneas que tengan un signo igual (=), luego con sed filtramos con dos expresiones, la primera elimina los espacios alrededor del igual (que a Bash no le gusta eso), y la segunda cambiará los ; por # sólo cuando una línea empiece por ; Todo eso se evaluará para extraer las variables.

Con awk leyendo cada línea

Podemos coger el mismo fichero simple.ini del ejemplo anterior.

Desde nuestro script para Bash queremos poder acceder al valor de servidor, puerto, usuario y password de una forma más o menos sencilla. Podemos hacer lo siguiente:

1
2
3
4
5
6
7
8
9
10
11
CFG_FILE=simple.ini

SERVER=$(awk -F "=" '/servidor/ {print $2}' "$CFG_FILE")
PORT=$(awk -F "=" '/puerto/ {print $2}' "$CFG_FILE")
USER=$(awk -F "=" '/usuario/ {print $2}' "$CFG_FILE")
PASS=$(awk -F "=" '/password/ {print $2}' "$CFG_FILE")

echo "Servidor: "$SERVER""
echo "Puerto: "$PORT""
echo "Usuario: "$USER""
echo "Password: "$PASS""

Como no hay muchos elementos en la configuración podemos hacerlo llamando a awk y será rápido. Si lo preferimos, podemos crear una pequeña función que haga la lectura, para no tener que poner la línea de awk todo el rato:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
CFG_FILE=simple.ini

function read_ini()
{
    local KEY="$1"
    local FILE="$2"
    awk -F "=" '/'"$KEY"'/ {print $2}' "$FILE"
}

SERVER=$(read_ini "servidor" "$CFG_FILE")
PORT=$(read_ini "puerto" "$CFG_FILE")
USER=$(read_ini "usuario" "$CFG_FILE")
PASS=$(read_ini "password" "$CFG_FILE")

echo "Servidor: "$SERVER""
echo "Puerto: "$PORT""
echo "Usuario: "$USER""
echo "Password: "$PASS""

Eso sí, se nos presentan algunos problemas:

  • Tenemos que saber que el fichero vamos a leerlo por completo 4 veces (tantas veces como lecturas hagamos) y las búsquedas de las palabras las haremos en todo el archivo. Lo que no es muy óptimo si tenemos muchas definiciones en la configuración.
  • Si tenemos varias veces la misma clave, veremos el valor completo de las dos claves. Es decir, si ponemos usuario dos veces, veremos los dos nombres de usuario seguidos al ver la variable (podemos solucionar esto con un exit dentro de awk, y aumentaremos algo el rendimiento).
  • Aunque el parseo es rápido no es exacto, si creamos una configuración en el INI llamada «nombre_usuario=test» ésta también se leerá como usuario. Y si comentamos un nombre de usuario, éste seguirá apareciendo.
  • Si ponemos espacios entre la clave y el igual o entre el igual y el valor, estos espacios figurarán en el valor obtenido. Deberíamos filtrarlos.
  • No tenemos secciones. Así que sólo servirá para cosas sencillas.

Vamos a completar un poco la llamada a awk en la función para solucionar algún problema, aunque el rendimiento bajará un 33% más o menos, aún así, sigue siendo rápido, pero realizaremos mejor el parseo:

1
2
3
4
5
6
function read_ini()
{
    local KEY="$1"
    local FILE="$2"
    awk -F "=" '/^\s*'"$KEY"'\s*/ {gsub(/^[ \t]+|[ \t]+$/, "", $2); print $2; exit}' "$FILE"
}

Ampliemos un poco más el script, para soportar secciones. Ahora tendremos un fichero ini así (secciones.ini):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
[principal]
servidor=db.dominio.com
puerto=3306
; Este no vale
nombre_usuario = aksjddd;
;usuario = test
usuario = armandoguerra
password=arreugodnamra32

[secundario]
servidor=database.dom.com
puerto=4417
usuario=zacariaslabasura
password=arusabalsairacaz

Y nuestro fichero para realizar la lectura sería así:

1
2
3
4
5
6
7
function read_ini()
{
        local SECTION="$1"
    local KEY="$2"
    local FILE="$3"
    sed -n '/^\['$SECTION'\]/,/^\[.*\]/p' "$FILE" | awk -F "=" '/^\s*'"$KEY"'\s*/ {gsub(/^[ \t]+|[ \t]+$/, "", $2); print $2; exit}'
}

Podríamos probarlo con estas llamadas:

1
2
3
4
5
6
7
8
9
10
11
12
13
SERVER=$(read_ini "principal" "servidor" "$CFG_FILE")
PORT=$(read_ini "principal" "puerto" "$CFG_FILE")
SERVER2=$(read_ini "secundario" "servidor" "$CFG_FILE")
PORT2=$(read_ini "secundario" "puerto" "$CFG_FILE")
USER=$(read_ini "principal" "usuario" "$CFG_FILE")
PASS=$(read_ini "principal" "password" "$CFG_FILE")

echo "Servidor: "$SERVER""
echo "Puerto: "$PORT""
echo "Servidor 2: "$SERVER2""
echo "Puerto 2: "$PORT2""
echo "Usuario: "$USER""
echo "Password: "$PASS""

El script, lógicamente tarda más del doble de tiempo, aunque todavía sigue siendo razonable (también depende mucho del tamaño del archivo, de los comentarios que tenga, etc). Además, seguimos haciendo una lectura por cada variable que queremos leer.

Parseo una vez, recopilacion de variables

Una de las cosas que no me gustan del primer método, además de la ejecución de código es que se declaran directamente las variables generadas para todo el script. Eso puede dar lugar a sobreescritura de variables que estemos utilizando (por ejemplo si encontramos en el .ini una variable del mismo nombre que una variable existente de nuestro script).
Así que una opción muy interesante sería poder incluirlas en un array. Y como Bash no soporta arrays multidimensionales podríamos hacer las claves del array con la forma SECCION_CLAVE.

Podemos hacer lo siguiente:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
CFG_FILE=secciones.ini
declare -A CONFIG

function read_ini()
{
        # Miramos la extensión extglob que nos permitirá utilizar
        # expresiones de sustitución complejas en Bash
        shopt -p extglob &> /dev/null
        local CHANGE_EXTGLOB=$?
        if [ $CHANGE_EXTGLOB = 1 ]; then
                # Establece la extensión
                shopt -s extglob
        fi

        local FILE="$1"
        # Nombre por defecto cuando no hay sección
        local CURRENT_SECTION="_default"

        local ini="$(<$FILE)"

        # Quitamos los \r usados en la nueva línea en formato DOS
        ini=${ini//$'\r'/}
        # Convertimos a un array
        IFS=$'\n' && ini=(${ini})
        # Borra espacios al principio y al final (trim)
        ini=(${ini[*]/#+([[:space:]])/})
        ini=(${ini[*]/%+([[:space:]])/})
        # Borra comentarios, con ; y con #
        ini=(${ini[*]//;*/})
        ini=(${ini[*]//\#*/})

        for l in ${ini[*]}; do
                if [[ "$l" =~ ^\[(.*)\]$ ]]; then
                        #echo "SECCION ${BASH_REMATCH[1]}"
                        CURRENT_SECTION="${BASH_REMATCH[1]}"
                # Los comentarios los podemos quitar antes
                # elif [[ "$l" =~ ^\; || "$l" =~ ^\# ]]; then
                #       echo "COMENTARIO $l"
                elif [[ "$l" =~ ^(.*)=(.*)$ ]]; then
                        local KEY="${CURRENT_SECTION}_"${BASH_REMATCH[1]%%+([[:space:]])}
                        local VALUE=${BASH_REMATCH[2]##+([[:space:]])}
                        CONFIG[$KEY]="$VALUE"
                        # echo "EVALUA ""$KEY"" = ""$VALUE"""

                else
                        #echo "ERROR EN $l"
                        false
                fi
        done

        if [ $CHANGE_EXTGLOB = 1 ]; then
                # Si tuvimos que meter la extensión, la quitamos
                shopt -u extglob
        fi
}

read_ini "$CFG_FILE"

echo "Servidor principal: "${CONFIG["principal_servidor"]}
echo "Servidor secundario: "${CONFIG["secundario_servidor"]}
echo "Puerto principal: "${CONFIG["principal_puerto"]}
echo "Puerto secundario: "${CONFIG["secundario_puerto"]}

Con este script, llamando a la función read_ini() y pasándole el nombre de archivo de configuración, rellenará el array CONFIG con la información del archivo. Para este script me he basado en este proyecto, basado a su vez en este otro. Sólo que este script no depende de eval, ni de source como ejemplos anteriores.
En mis pruebas, este método tiene un rendimiento algo superior al método de awk del principio. Si os fijáis, no recurro a herramientas externas a Bash. Además, sólo se hace una lectura del fichero, se almacena en un buffer y la función read_ini() se encarga de poner todo en el array, por lo tanto, cada vez que necesitemos conseguir un valor de configuración, sólo leemos del array. Eso lo hará todo mucho más rápido.

¿Qué sistema utilizas para la configuración de tus scripts?

Dejo esta pregunta abierta para vuestros comentarios. ¿Usas archivos Json? ¿Utilizas un script en Python que haga de puente? ¿Lees de una base de datos?
Foto principal: unsplash-logoChris Kristiansen

También podría interesarte....

There are 28 comments left Ir a comentario

  1. Pingback: Leer ficheros de configuración INI desde nuestros scripts en BASH | PlanetaLibre /

  2. 3rn3st0 /
    Usando Mozilla Firefox Mozilla Firefox 59.0 en Linux Linux

    Aunque tengo a Poesía Binaria dentro de mis RSS desde hace varios meses, no es sino hasta hace una o dos semanas que he venido revisando en detalle tus publicaciones.

    Lo referente a Bash Scripting me ha resultado de lo más ilustrativo y enriquecedor.

    Quiero pues, agradecer el empeño y dedicación que has puesto en las publicaciones que haces.

    Desde Venezuela, un gran saludo admirado y agradecido.

    1. Gaspar Fernández / Post Author
      Usando Mozilla Firefox Mozilla Firefox 59.0 en Ubuntu Linux Ubuntu Linux

      Muchísimas gracias 3rn3sto!! Comentarios como este me animan mucho a seguir escribiendo! Es un honor tener seguidores como tú.

      Un abrazo!

  3. Victor /
    Usando Mozilla Firefox Mozilla Firefox 60.0 en Ubuntu Linux Ubuntu Linux

    Tu trabajo es espectacular, nos hace redefinir los conocimientos, y ver nuevas formas de hacer las cosas.
    Tus artículos son de excelente calidad didáctica.
    Mi mas cálido agradecimiento por lo que hacer para todos.

    Victor
    Uruguay

    1. Gaspar Fernández / Post Author
      Usando Mozilla Firefox Mozilla Firefox 61.0 en Ubuntu Linux Ubuntu Linux

      Muchísimas gracias Victor! Comentarios como el tuyo me hacen seguir adelante y buscar nuevos temas. Un abrazo

  4. jesse99 /
    Usando Google Chrome Google Chrome 116.0.0.0 en Windows Windows NT

    Me impresiona tu capacidad para explicar conceptos complejos de una forma tan clara y accesible. Esta publicación es un recurso valioso para cualquier crossover grid persona interesada en el tema.

  5. 메이저사이트 /
    Usando Google Chrome Google Chrome 118.0.0.0 en Windows Windows NT

    After reading this write-up, I honestly think this excellent website needs a lot more consideration. I return frequently to learn more, thank you for this information.메이저사이트

  6. 메이저놀이터 /
    Usando Google Chrome Google Chrome 118.0.0.0 en Windows Windows NT

    Can certainly very well generate with identical issues! Allowed to help in this article you can learn it should view.메이저놀이터

  7. https://totogorae.com/ /
    Usando Google Chrome Google Chrome 118.0.0.0 en Windows Windows NT

    First-rate subject theme, the same texts are often Once i have no idea of provided that they usually are competing along with your career accessible https://totogorae.com/

  8. 토토사이트 추천 /
    Usando Google Chrome Google Chrome 118.0.0.0 en Windows Windows NT

    It will be world class, then again look into the info in this particular address 토토사이트 추천

  9. https://totofist.com/ /
    Usando Google Chrome Google Chrome 118.0.0.0 en Windows Windows NT

    At this time there you will receive devoid of price tag, check out the primary connected with these truth https://totofist.com/

  10. Jon /
    Usando Google Chrome Google Chrome 119.0.0.0 en Windows Windows NT

    Absolutely, using INI format files for configuration brings simplicity and readability to settings. | https://www.baltimoreconcreteservices.com

  11. jsimitseo /
    Usando Google Chrome Google Chrome 120.0.0.0 en Windows Windows NT

    The sheer size and selection of these web slots are mind-blowing. Great job. สล็อตเว็บใหญ่

  12. jsimitseo /
    Usando Google Chrome Google Chrome 120.0.0.0 en Windows Windows NT

    I’ve had bad experiences with slots breaking too easily. เว็บสล็อตแตกง่าย

  13. jsimitseo /
    Usando Google Chrome Google Chrome 120.0.0.0 en Windows Windows NT

    I truly welcome this superb post that you have accommodated us. I guarantee this would be advantageous for the majority of the general population. เว็บสล็อตทุกค่าย

  14. jsimitseo /
    Usando Google Chrome Google Chrome 120.0.0.0 en Windows Windows NT

    Exceptionally fascinating data, worth suggesting. Be that as it may, I suggest this: สล็อตโรม่า

  15. jsimitseo /
    Usando Google Chrome Google Chrome 120.0.0.0 en Windows Windows NT

    For some individuals this is critical, so look at my profile: ป๊อกเด้งออนไลน์

  16. jsimitseo /
    Usando Google Chrome Google Chrome 121.0.0.0 en Windows Windows NT

    I likewise composed an article on a comparative subject will discover it at compose what you think. concierge doctor naples

  17. Rank Xone /
    Usando Google Chrome Google Chrome 121.0.0.0 en Windows Windows NT

    One of the best things about online casinos is the variety of games. Whether you’re into slots, blackjack, or roulette, there’s always something for everyone. 프리카지노 쿠폰

  18. Rank Xone /
    Usando Google Chrome Google Chrome 122.0.0.0 en Windows Windows NT

    I need you to thank for your season of this great read!!! I definately appreciate each and every piece of it and I have you bookmarked to look at new stuff of your blog an unquestionable requirement read blog! https://manclubs.net/

  19. Rank Xone /
    Usando Google Chrome Google Chrome 122.0.0.0 en Windows Windows NT

    This 8kg heavy mug just for coffee; it’s a reminder of the power of words in our digital age!

  20. Rank Xone /
    Usando Google Chrome Google Chrome 122.0.0.0 en Windows Windows NT

    I am overpowered by your post with such a decent theme. Typically I visit your web journals and get refreshed through the data you incorporate yet the present blog would be the most obvious. Well done! palma taxi

  21. Rank Xone /
    Usando Google Chrome Google Chrome 122.0.0.0 en Windows Windows NT

    Nice information, valuable and excellent design, as share good stuff with good ideas and concepts, lots of great information and inspiration, both of which I need, thanks to offer such a helpful information here. Raccoon Removal Brampton

  22. Rank Xone /
    Usando Google Chrome Google Chrome 122.0.0.0 en Windows Windows NT

    This is a great article, Given such a great amount of information in it, These kind of articles keeps the clients enthusiasm for the site, and continue sharing more … good fortunes. Squirrel Removal

  23. Rank Xone /
    Usando Google Chrome Google Chrome 122.0.0.0 en Windows Windows NT

    The website loading speed is amazing. It kind of feels that you’re doing any distinctive trick. Moreover, The contents are masterpiece. you have done a fantastic activity on this subject! Bus rental Europe

  24. Rank Xone /
    Usando Google Chrome Google Chrome 122.0.0.0 en Windows Windows NT

    The casino is where the action is, and the chips fall where they may. 로즈카지노

  25. williamSEO /
    Usando Mozilla Firefox Mozilla Firefox 123.0 en Windows Windows NT

    Well we extremely get a kick out of the chance to visit this site, numerous valuable data we can arrive. Raccoon Removal

  26. WilliamSEO /
    Usando Google Chrome Google Chrome 122.0.0.0 en Windows Windows NT

    llo there mates, it is incredible composed piece completely characterized, proceed with the great work always. slot mahjong ways 2

Leave a Reply