Come analizzare e convertire il file ini in variabili di array bash?


12

Sto cercando di convertire un file ini in variabili di array bash. Il campione ini è il seguente:

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

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

quindi questi diventano:

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

e così via.

In questo momento, ho potuto venire solo con questo comando

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

Inoltre, un altro problema è che non prende =in considerazione gli spazi . Penso che sedsia probabilmente più adatto a questo lavoro, ma non so come conservare e memorizzare una variabile temporanea per il nome della sezione sed.

Qualche idea su come farlo?


Se esiste un altro modo efficace per farlo, non esitare a pubblicare anche la tua soluzione :)
Flint


Per una soluzione semplice, controlla: Come posso ottenere un valore INI all'interno di uno script di shell? su stackoverflow SE.
Kenorb,

Risposte:


10

Gawk accetta le espressioni regolari come delimitatori di campo. Quanto segue elimina gli spazi attorno al segno uguale, ma li conserva nel resto della linea. Le virgolette vengono aggiunte attorno al valore in modo che tali spazi, se presenti, vengano conservati quando viene eseguita l'assegnazione di Bash. Suppongo che i nomi delle sezioni saranno variabili numeriche, ma se si utilizza Bash 4, sarebbe facile adattarlo per utilizzare gli array associativi con i nomi delle sezioni stessi come indici.

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

Nota che potresti voler fare anche la rimozione dello spazio che Khaled mostra (su solo $ 1 e sezione) poiché i nomi delle variabili di Bash non possono contenere spazi.

Inoltre, questo metodo non funzionerà se i valori contengono segni di uguale.

Un'altra tecnica sarebbe quella di utilizzare un while readloop Bash ed eseguire le assegnazioni mentre il file viene letto, utilizzando declarequale è al sicuro dalla maggior parte dei contenuti dannosi.

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

Ancora una volta, gli array associativi potrebbero essere abbastanza facilmente supportati.


1
Informazioni molto belle e mi piace in particolare la seconda tecnica poiché utilizza la funzione integrata bash, invece di fare affidamento su un comando esterno.
Flint,

@TonyBarganski: può essere modificato in una chiamata AWK invece di convogliarne uno nell'altro.
In pausa fino a ulteriore avviso.

10

Vorrei usare un semplice script Python per questo lavoro poiché ha incorporato il parser 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)

e poi in 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]}

Certo, ci sono implementazioni più brevi in ​​awk, ma penso che questo sia più leggibile e più facile da mantenere.


3
Nelle versioni bash precedenti alla 4.2 è necessario dichiarare un array associato prima di riempirlo, ad es.print "declare -A %s" % (sec)
Felix Eve,

2
Invece di eval:source <(cat in.ini | ./ini2arr.py)
In pausa fino a nuovo avviso.

3

Se si desidera eliminare gli spazi extra, è possibile utilizzare la funzione integrata gsub. Ad esempio, puoi aggiungere:

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

Questo rimuoverà tutti gli spazi. Se si desidera rimuovere spazi all'inizio o alla fine del token, è possibile utilizzare

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

Trucchi fantastici. Non sapevo che ci fosse una tale funzione integrata :)
Flint

0

Ecco una soluzione bash pura.

Questa è una versione nuova e migliorata di ciò che ha pubblicato chilladx:

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

Per un esempio iniziale molto semplice da seguire: dopo aver scaricato questo, basta copiare i file bash-ini-parsere scripts/file.ininella stessa directory, quindi creare uno script di test client usando l'esempio che ho fornito di seguito anche nella stessa directory.

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

Ecco alcuni ulteriori miglioramenti che ho apportato allo script bash-ini-parser ...

Se vuoi essere in grado di leggere i file ini con terminazioni di riga di Windows e Unix, aggiungi questa riga alla funzione cfg_parser immediatamente dopo quella che legge il file:

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

Se si desidera leggere file con autorizzazioni di accesso restrittive, aggiungere questa funzione opzionale:

# 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  
}

Ho dovuto votare a causa di chmod 777. Mentre nel migliore dei casi è una pratica losca, non c'è sicuramente bisogno di rendere eseguibile il file ini. Un approccio migliore sarebbe quello di utilizzare sudoper leggere il file, non per pasticciare con le autorizzazioni.
Richlv,

@Richlv Ok. Apprezzo la spiegazione del voto negativo. Ma questa è una piccola parte di questo, che ha un significato minimo per quanto riguarda la risposta alla domanda nel suo insieme. La "risposta" è il link: github.com/albfan/bash-ini-parser . Invece di votare in giù l'intera cosa, per quella che è già un'etichetta una funzione wrapper opzionale, avresti potuto suggerire una modifica.
BuvinJ,

0

Partendo sempre dal presupposto che ConfigParser di Python sia in giro, è possibile creare una funzione di supporto della shell come questa:

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

$IFACEe $paramsono rispettivamente la sezione il parametro.

Questo helper consente quindi chiamate come:

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

Spero che sia di aiuto!


0

Se hai Git disponibile e stai bene con il vincolo di non poter usare i caratteri di sottolineatura nei nomi delle chiavi, puoi usare git configcome un parser / editor INI generico.

Gestirà l'analisi della coppia chiave / valore intorno allo =spazio bianco scarto e scartato, oltre a ottenere commenti (entrambi ;e #) e digitare la coercizione praticamente gratuitamente. Di seguito ho incluso un esempio di lavoro completo per l'input dell'OP .inie l'output desiderato (array associativi di Bash).

Tuttavia, dato un file di configurazione come questo

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

[section2]
    anothersetting = 42

... purché tu abbia solo bisogno di una soluzione rapida e sporca e non sei sposato con l'idea di avere le impostazioni in un array associativo Bash, potresti cavartela con un minimo di:

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

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

che crea variabili di ambiente denominate sectionname_variablenamenell'ambiente corrente. Questo, ovviamente, funziona solo se puoi fidarti che nessuno dei tuoi valori conterrà mai un punto o uno spazio bianco (vedi sotto per una soluzione più solida).

Altri semplici esempi

Recupero di valori arbitrari, utilizzando una funzione shell per salvare la digitazione:

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

Un alias sarebbe OK, anche qui, ma quelli normalmente non sono espansi in uno script di shell [ 1 ], e comunque gli alias sono sostituiti dalle funzioni di shell "per quasi tutti gli scopi", [ 2 ], secondo la pagina man di 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 l' --typeopzione, puoi "canonicalizzare" impostazioni specifiche come numeri interi, valori booleani o percorsi (espandendosi automaticamente ~):

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

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

Esempio di quick-and-dirty leggermente più robusto

Rendi disponibili tutte le variabili mytool.inicome SECTIONNAME_VARIABLENAMEnell'ambiente corrente, preservando gli spazi bianchi interni nei valori chiave:

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

Quello che sta facendo l'espressione sed, in inglese, è

  1. trovare un gruppo di personaggi non periodici fino a un punto, ricordandolo come \1allora
  2. trovare un gruppo di caratteri fino a un segno di uguale, ricordandolo come \2, e
  3. trovare tutti i caratteri dopo il segno di uguale come \3
  4. infine, nella stringa di sostituzione
    • il nome della sezione + il nome della variabile è maiuscolo, e
    • la parte del valore è racchiusa tra virgolette, nel caso contenga caratteri che hanno un significato speciale per la shell se non quotati (come gli spazi)

Le sequenze \Ue \Enella stringa di sostituzione (che in maiuscolo quella parte della stringa di sostituzione) sono l' sedestensione GNU . Su macOS e BSD, useresti solo -eespressioni multiple per ottenere lo stesso effetto.

Trattare con virgolette incorporate e spazi bianchi nei nomi delle sezioni (che git configconsente) viene lasciato come esercizio per il lettore.:)

Utilizzo dei nomi delle sezioni come chiavi in ​​un array associativo Bash

Dato:

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

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

Ciò produrrà il risultato richiesto dall'OP, semplicemente riorganizzando alcune delle catture nell'espressione di sostituzione sed e funzionerà bene senza GNU sed:

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

Prevedo che potrebbero esserci delle difficoltà con la citazione di un .inifile del mondo reale , ma funziona per l'esempio fornito. Risultato:

declare -p {session,path}
# result:
# declare -A session=([barfoo]="bar" [foobar]="foo" )
# declare -A path=([barfoo]="/some/path" [foobar]="/some/path" )
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.