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.
Tabla de contenidos
¿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: Chris Kristiansen
Pingback: Leer ficheros de configuración INI desde nuestros scripts en BASH | PlanetaLibre /
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.
Muchísimas gracias 3rn3sto!! Comentarios como este me animan mucho a seguir escribiendo! Es un honor tener seguidores como tú.
Un abrazo!
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
Muchísimas gracias Victor! Comentarios como el tuyo me hacen seguir adelante y buscar nuevos temas. Un abrazo
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.
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.메이저사이트
Can certainly very well generate with identical issues! Allowed to help in this article you can learn it should view.메이저놀이터
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/
It will be world class, then again look into the info in this particular address 토토사이트 추천
At this time there you will receive devoid of price tag, check out the primary connected with these truth https://totofist.com/
Absolutely, using INI format files for configuration brings simplicity and readability to settings. | https://www.baltimoreconcreteservices.com
The sheer size and selection of these web slots are mind-blowing. Great job. สล็อตเว็บใหญ่
I’ve had bad experiences with slots breaking too easily. เว็บสล็อตแตกง่าย
I truly welcome this superb post that you have accommodated us. I guarantee this would be advantageous for the majority of the general population. เว็บสล็อตทุกค่าย
Exceptionally fascinating data, worth suggesting. Be that as it may, I suggest this: สล็อตโรม่า
For some individuals this is critical, so look at my profile: ป๊อกเด้งออนไลน์
I likewise composed an article on a comparative subject will discover it at compose what you think. concierge doctor naples
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. 프리카지노 쿠폰
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/
This 8kg heavy mug just for coffee; it’s a reminder of the power of words in our digital age!
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
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
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
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
The casino is where the action is, and the chips fall where they may. 로즈카지노
Well we extremely get a kick out of the chance to visit this site, numerous valuable data we can arrive. Raccoon Removal
llo there mates, it is incredible composed piece completely characterized, proceed with the great work always. slot mahjong ways 2
I need to express profound gratitude to you. I have bookmark your site for future updates. situs slot gacor
Hello there to everyone, here everybody is sharing such learning, so it’s critical to see this website, and I used to visit this blog day by day Buddhism Bhutan
Hallo guys, selamat datang di situs OKEPLAY777, pernahkah anda mendengar atau sudah bermain disini ?, Mari kita bahas sedikit tentang kelebihan situs slot online ini, sebagai situs slot gacor hari ini yang menggunakan slot server thailand terbaru gampang menang dengan winrate tertinggi di bandingkan situs lain, bermain di OKEPLAY777 memiliki experience yang berbeda pada saat mendapatkan kemenangan sensasional hanya dengan modal deposit slot dana Rp.15,000 dan menang berapapun di bayar tanpa potongan.
But casinos aren’t just about gambling. They’re also social hubs where people from all walks of life come together to unwind, socialize, and maybe even strike up a conversation with Lady Luck herself. Whether you’re sipping cocktails at the bar, enjoying a gourmet meal at the restaurant, or catching a live show, there’s never a dull moment in a casino. 한국야동
One of the most alluring aspects of casinos is the potential for big wins. Whether it’s hitting the jackpot on a slot machine or outplaying opponents at the poker table, the thrill of winning can be exhilarating. However, it’s essential to remember that gambling also carries risks, and it’s possible to lose money as well. 우리카지노
Hello there to everyone, here everybody is sharing such learning, so it’s critical to see this website, and I used to visit this blog day by day cfa level 1 fail
Hello there to everyone, here everybody is sharing such learning, so it’s critical to see this website, and I used to visit this blog day by day rolling serving cart
This is a great post I seen because of offer it. It is truly what I needed to see seek in future you will proceed after sharing such a magnificent post. corners estatisticas
Educational Technology , this is a paradise for technology lovers! Combining modern technology and education to make students learn more excitingly. 作业代写
The core competitiveness of an excellent ghostwriting agency lies in having a professional team. 北美代写
ufa789 เว็บแทงบอลที่ใหญ่ และการเงินมั่นคงที่สุด สล็อตออนไลน์ ไม่มีขั้นต่ำ ใช้ทุนน้อย เล่นได้ทุกเกม
ทางเข้า ufa789 ผู้ให้บริการสปอร์ตออนไลน์ และคาสิโนเต็มรูปแบบ เล่นเพลินเกินห้ามใจ ทำกำไรได้ทุกวัน
Well we extremely get a kick out of the chance to visit this site, numerous valuable data we can arrive. funny sayings panties
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! bachelorette gifts
Yet amidst the accolades and achievements, Hadley Palmer remains grounded, never forgetting the values that guide them. Integrity, compassion, and a commitment to excellence are the pillars upon which their empire stands, inspiring admiration and respect from peers and admirers alike. Hadley Palmer