¿Cómo analizar y convertir el archivo ini en variables de matriz bash?

12

Estoy tratando de convertir un archivo ini en variables de matriz bash. La muestra ini es la siguiente:

[foobar]
session=foo
path=/some/path

[barfoo]
session=bar
path=/some/path

entonces estos se convierten en:

session[foobar]=foo
path[foobar]=/some/path
session[barfoo]=bar

y así.

En este momento, solo se me ocurre este comando

awk -F'=' '{ if ($1 ~ /^\[/) section=$1; else if ($1 !~ /^$/) print $1 section "=" $2 }'

Además, otro problema es que no toma =en cuenta los espacios cercanos . Creo que sedprobablemente sea más adecuado para este trabajo, pero no sé cómo mantener y almacenar una variable temporal para el nombre de la sección sed.

¿Alguna idea de cómo hacer esto?

Pedernal
fuente
Si hay otra manera eficiente de hacer esto, siéntase libre de publicar su solución también :)
Flint
Para una solución simple, verifique: ¿Cómo obtengo un valor INI dentro de un script de shell? en stackoverflow SE.
kenorb

Respuestas:

10

Gawk acepta expresiones regulares como delimitadores de campo. Lo siguiente elimina espacios alrededor del signo igual, pero los conserva en el resto de la línea. Las comillas se agregan alrededor del valor para que esos espacios, si los hay, se conserven cuando se realiza la asignación Bash. Supongo que los nombres de las secciones serán variables numéricas, pero si está usando Bash 4, sería fácil adaptar esto para usar matrices asociativas con los nombres de las secciones como índices.

awk -F ' *= *' '{ if ($1 ~ /^\[/) section=$1; else if ($1 !~ /^$/) print $1 section "=" "\"" $2 "\"" }'

Tenga en cuenta que es posible que también desee eliminar el espacio que muestra Khaled (solo en $ 1 y sección) ya que los nombres de variables Bash no pueden contener espacios.

Además, este método no funcionará si los valores contienen signos iguales.

Otra técnica sería utilizar un while readbucle Bash y realizar las asignaciones a medida que se lee el archivo, utilizando el declareque está a salvo de la mayoría del contenido malicioso.

foobar=1
barfoo=2  # or you could increment an index variable each time a section is found
while IFS='= ' read var val
do
    if [[ $var == \[*] ]]
    then
        section=$var
    elif [[ $val ]]
    then
        declare "$var$section=$val"
    fi
done < filename

Una vez más, las matrices asociativas podrían ser fácilmente compatibles.

Pausado hasta nuevo aviso.
fuente
1
Muy buena información y me gusta especialmente la segunda técnica, ya que utiliza la función integrada bash, en lugar de depender de un comando externo.
Flint
@TonyBarganski: Eso se puede modificar en una llamada AWK en lugar de conectar una en otra.
Pausado hasta nuevo aviso.
10

Usaría un script de Python simple para este trabajo ya que ha incorporado el analizador INI :

#!/usr/bin/env python

import sys, ConfigParser

config = ConfigParser.ConfigParser()
config.readfp(sys.stdin)

for sec in config.sections():
    print "declare -A %s" % (sec)
    for key, val in config.items(sec):
        print '%s[%s]="%s"' % (sec, key, val)

y luego en bash:

#!/bin/bash

# load the in.ini INI file to current BASH - quoted to preserve line breaks
eval "$(cat in.ini  | ./ini2arr.py)"

# test it:
echo ${barfoo[session]}

Claro, hay implementaciones más cortas en awk, pero creo que esto es más legible y más fácil de mantener.

Michał Šrajer
fuente
3
En las versiones de bash anteriores a 4.2 es necesario declarar una matriz asociada antes de llenarla, por ejemploprint "declare -A %s" % (sec)
Felix Eve
2
En lugar de eval:source <(cat in.ini | ./ini2arr.py)
Pausado hasta nuevo aviso.
3

Si desea eliminar los espacios adicionales, puede usar la función incorporada gsub. Por ejemplo, puede agregar:

gsub(/ /, "", $1);

Esto eliminará todos los espacios. Si desea eliminar espacios al principio o al final del token, puede usar

gsub(/^ /, "", $1);
gsub(/ $/, "", $1);
Khaled
fuente
Trucos geniales. No sabía que hay una función tan integrada :)
Flint
0

Aquí hay una solución de bash puro.

Esta es una versión nueva y mejorada de lo que chilladx publicó:

https://github.com/albfan/bash-ini-parser

Para un ejemplo inicial realmente fácil de seguir: después de descargar esto, simplemente copie los archivos bash-ini-parsery scripts/file.inien el mismo directorio, luego cree un script de prueba del cliente usando el ejemplo que proporcioné a continuación en ese mismo directorio también.

source ./bash-ini-parser
cfg_parser "./file.ini"
cfg_section_sec2
echo "var2=$var2"
echo "var5[*]=${var5[*]}"
echo "var5[1]=${var5[1]}"

Aquí hay algunas mejoras adicionales que hice al script bash-ini-parser ...

Si desea poder leer archivos ini con terminaciones de línea de Windows y Unix, agregue esta línea a la función cfg_parser inmediatamente después de la que lee el archivo:

ini=$(echo "$ini"|tr -d '\r') # remove carriage returns

Si desea leer archivos que tienen permisos de acceso restrictivos, agregue esta función opcional:

# Enable the cfg_parser to read "locked" files
function sudo_cfg_parser {

    # Get the file argument
    file=$1

    # If not "root", enable the "sudo" prefix
    sudoPrefix=
    if [[ $EUID -ne 0 ]]; then sudoPrefix=sudo; fi

    # Save the file permissions, then "unlock" the file
    saved_permissions=$($sudoPrefix stat -c %a $file)
    $sudoPrefix chmod 777 $file

    # Call the standard cfg_parser function
    cfg_parser $file

    # Restore the original permissions
    $sudoPrefix chmod $saved_permissions $file  
}
BuvinJ
fuente
Tuve que rechazar debido a chmod 777. Si bien es una práctica sospechosa en el mejor de los casos, seguramente no hay necesidad de hacer que el archivo ini sea ejecutable. Un mejor enfoque sería usarlo sudopara leer el archivo, no meterse con los permisos.
Richlv
@Richlv Ok. Agradezco la explicación del voto negativo. Pero, esa es una pequeña parte de esto, que tiene una importancia mínima en cuanto a responder la pregunta en su conjunto. La "respuesta" es el enlace: github.com/albfan/bash-ini-parser . En lugar de votar hacia abajo todo, para lo que ya está etiquetado como una función de contenedor opcional, podría haber sugerido una edición.
BuvinJ
0

Siempre suponiendo que tenga ConfigParser de Python, uno puede construir una función auxiliar de shell como esta:

get_network_value()
{
    cat <<EOF | python
import ConfigParser
config = ConfigParser.ConfigParser()
config.read('network.ini')
print (config.get('$IFACE','$param'))
EOF
}

$IFACEy $param son la sección respectivamente el parámetro.

Este ayudante luego permite llamadas como:

address=`param=address get_network_value` || exit 1
netmask=`param=netmask get_network_value` || exit 1
gateway=`param=gateway get_network_value` || exit 1

¡Espero que esto ayude!

Matthias Dieter Wallnöfer
fuente
0

Si tiene Git disponible y está de acuerdo con la restricción de no poder usar guiones bajos en los nombres de clave, puede usarlo git configcomo un analizador / editor INI de propósito general.

Manejará el análisis del par clave / valor de todo el =espacio en blanco y descartará insignificantes, además obtendrá comentarios (ambos ;y #) y la coerción de tipo básicamente de forma gratuita. He incluido un ejemplo de trabajo completo para la entrada del OP .iniy la salida deseada (matrices asociativas de Bash), a continuación.

Sin embargo, dado un archivo de configuración como este

; mytool.ini
[section1]
    inputdir = ~/some/dir
    enablesomefeature = true
    enablesomeotherfeature = yes
    greeting = Bonjour, Monde!

[section2]
    anothersetting = 42

... siempre que solo necesite una solución rápida y sucia, y no esté casado con la idea de tener la configuración en una matriz asociativa de Bash, puede salirse con la suya con tan solo:

eval $(git config -f mytool.ini --list | tr . _)

# or if 'eval' skeeves you out excessively
source <(git config -f mytool.ini --list | tr . _)

que crea variables de entorno llamadas sectionname_variablename en el entorno actual. Esto, por supuesto, solo funciona si puede confiar en que ninguno de sus valores contendrá un punto o espacio en blanco (consulte a continuación para obtener una solución más sólida).

Otros ejemplos simples

Obteniendo valores arbitrarios, usando una función de shell para guardar la escritura:

function myini() { git config -f mytool.ini; }

Aquí también estaría bien un alias, pero normalmente no se expanden en un script de shell [ 1 ], y de todos modos los alias son reemplazados por las funciones de shell "para casi cualquier propósito", [ 2 ], según la página del manual de Bash .

myini --list
# result:
# section1.inputdir=~/some/dir
# section1.enablesomefeature=true
# section1.enablesomeotherfeature=yes
# section2.anothersetting=42

myini --get section1.inputdir
# result:
# ~/some/dir

Con la --typeopción, puede "canonizar" configuraciones específicas como enteros, booleanos o rutas (expandiéndose automáticamente ~):

myini --get --type=path section1.inputdir  # value '~/some/dir'
# result:
# /home/myuser/some/dir

myini --get --type=bool section1.enablesomeotherfeature  # value 'yes'
# result:
# true

Ejemplo un poco más robusto, rápido y sucio

Haga que todas las variables mytool.iniestén disponibles como SECTIONNAME_VARIABLENAMEen el entorno actual, conservando espacios en blanco internos en valores clave:

source <(
    git config -f mytool.ini --list \
      | sed 's/\([^.]*\)\.\(.*\)=\(.*\)/\U\1_\2\E="\3"/'
)

Lo que está haciendo la expresión sed, en inglés, es

  1. encontrar un montón de personajes que no sean de período hasta un período, recordando eso como \1, luego
  2. encontrar un montón de caracteres hasta un signo igual, recordando eso como \2, y
  3. encontrar todos los caracteres después del signo igual como \3
  4. finalmente, en la cadena de reemplazo
    • el nombre de la sección + el nombre de la variable está en mayúsculas y
    • la parte del valor tiene comillas dobles, en caso de que contenga caracteres que tengan un significado especial para el shell si no están entre comillas (como espacios en blanco)

Las secuencias \Uy \Een la cadena de reemplazo (que en mayúscula esa parte de la cadena de reemplazo) son sedextensión GNU . En macOS y BSD, solo usaría múltiples-e expresiones para lograr el mismo efecto.

Tratar con comillas y espacios en blanco incrustados en los nombres de las secciones (lo que git configpermite) se deja como ejercicio para el lector.:)

Usar nombres de sección como claves en una matriz asociativa de Bash

Dado:

; foo.ini
[foobar]
session=foo
path=/some/path

[barfoo]
session=bar
path=/some/path

Esto producirá el resultado que solicita el OP, simplemente reorganizando algunas de las capturas en la expresión de reemplazo de sed, y funcionará bien sin GNU sed:

source <(
    git config -f foo.ini --list \
      | sed 's/\([^.]*\)\.\(.*\)=\(.*\)/declare -A \2["\1"]="\3"/'
)

Predigo que podría haber algunos desafíos al citar un .iniarchivo del mundo real , pero funciona para el ejemplo proporcionado. Resultado:

declare -p {session,path}
# result:
# declare -A session=([barfoo]="bar" [foobar]="foo" )
# declare -A path=([barfoo]="/some/path" [foobar]="/some/path" )
El tipo respeta
fuente