Como analisar e converter arquivo ini em variáveis ​​de array bash?

12

Estou tentando converter um arquivo ini em variáveis ​​de matriz bash. A amostra ini é como abaixo:

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

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

então eles se tornam:

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

e assim por diante.

No momento, eu poderia criar apenas esse comando

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

Além disso, outro problema é que ele não leva =em consideração os espaços próximos . Acho que sedprovavelmente é mais adequado para esse trabalho, mas não sei como armazenar e armazenar uma variável temporária para o nome da seção sed.

Então, alguma idéia de como fazer isso?

pedra
fonte
Se há outra maneira eficiente de fazer isso, sinta-se livre para postar sua solução também :)
Flint
Para uma solução simples, verifique: Como pego um valor INI em um script de shell? no stackoverflow SE.
kenorb

Respostas:

10

Gawk aceita expressões regulares como delimitadores de campo. A seguir, elimina os espaços ao redor do sinal de igual, mas os preserva no restante da linha. As aspas são adicionadas em torno do valor para que esses espaços, se houver, sejam preservados quando a atribuição do Bash for executada. Estou assumindo que os nomes das seções serão variáveis ​​numéricas, mas se você estiver usando o Bash 4, seria fácil adaptar isso para usar matrizes associativas com os próprios nomes das seções como índices.

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

Observe que você também pode fazer a remoção de espaço que Khaled mostra (em apenas US $ 1 e seção), pois os nomes de variáveis ​​do Bash não podem conter espaços.

Além disso, esse método não funcionará se os valores contiverem sinais de igual.

Outra técnica seria usar um while readloop Bash e executar as atribuições à medida que o arquivo é lido, o declareque é seguro para a maioria dos conteúdos maliciosos.

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

Novamente, matrizes associativas poderiam ser facilmente suportadas.

Pausado até novo aviso.
fonte
1
Informações muito boas e eu particularmente gosto da segunda técnica, pois ela usa a função incorporada do bash, em vez de confiar no comando externo.
Flint
@ TonyBarganski: Isso pode ser modificado em uma chamada AWK em vez de canalizar uma para outra.
Pausado até novo aviso.
10

Eu usaria um script python simples para este trabalho, pois ele foi incorporado no analisador 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 depois no 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, existem implementações mais curtas no awk, mas acho que isso é mais legível e mais fácil de manter.

Michał Šrajer
fonte
3
Nas versões bash anteriores à 4.2, é necessário declarar uma matriz associada antes de preenchê-la, por exemplo:print "declare -A %s" % (sec)
Felix Eve
2
Em vez de eval:source <(cat in.ini | ./ini2arr.py)
Pausado até novo aviso.
3

Se você deseja eliminar os espaços extras, pode usar a função interna gsub. Por exemplo, você pode adicionar:

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

Isso removerá todos os espaços. Se você deseja remover espaços no início ou no final do token, pode usar

gsub(/^ /, "", $1);
gsub(/ $/, "", $1);
Khaled
fonte
Truques legais. Não sabia que há tal um built-in função :)
Flint
0

Aqui está uma solução pura para o bash.

Esta é uma versão nova e aprimorada do que chilladx postou:

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

Para um exemplo inicial realmente fácil de seguir: Depois de fazer o download, copie os arquivos bash-ini-parsere scripts/file.inipara o mesmo diretório e crie um script de teste do cliente usando o exemplo que forneci abaixo para o mesmo diretório também.

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

Aqui estão algumas outras melhorias que eu fiz no script bash-ini-parser ...

Se você quiser ler arquivos ini com finais de linha do Windows e Unix, adicione essa linha à função cfg_parser imediatamente após a que lê o arquivo:

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

Se você quiser ler arquivos com permissões de acesso restritivas, adicione esta função 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
fonte
Teve que votar por causa de chmod 777. Enquanto uma prática obscura, na melhor das hipóteses, certamente não há necessidade de tornar o arquivo ini executável. Uma abordagem melhor seria usar sudopara ler o arquivo, não mexer nas permissões.
19418 Richlv
@Richlv Ok. Eu aprecio a explicação de voto negativo. Mas, isso é uma pequena parte disso, que tem um significado mínimo no que diz respeito à resposta à pergunta como um todo. A "resposta" é o link: github.com/albfan/bash-ini-parser . Em vez de fazer o voto negativo, para o que já é rotulado como uma função opcional do wrapper, você poderia sugerir uma edição.
BuvinJ
0

Sempre assumindo que o ConfigParser do Python esteja por perto, pode-se criar uma função 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
}

$IFACEe $paramsão a seção, respectivamente, o parâmetro

Esse ajudante permite chamadas 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 isto ajude!

Matthias Dieter Wallnöfer
fonte
0

Se você tiver o Git disponível e estiver com a restrição de não poder usar sublinhados nos nomes das chaves, poderá usá-lo git configcomo um analisador / editor INI de uso geral.

Ele manipulará a análise do par de chave / valor em torno do =espaço em branco insignificante e descartará, além de obter comentários (ambos ;e #) e digitar coerção basicamente de graça. Incluí um exemplo de trabalho completo para a entrada do OP .inie a saída desejada (matrizes associativas Bash), abaixo.

No entanto, dado um arquivo de configuração como este

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

[section2]
    anothersetting = 42

… Desde que você precise apenas de uma solução rápida e suja e não seja casado com a idéia de ter as configurações em uma matriz associativa do Bash, você pode se dar bem com o seguinte:

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

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

que cria variáveis ​​de ambiente nomeadas sectionname_variablenameno ambiente atual. Obviamente, isso só funciona se você puder confiar que nenhum dos seus valores conterá um período ou espaço em branco (veja abaixo uma solução mais robusta).

Outros exemplos simples

Buscando valores arbitrários, usando uma função shell para salvar a digitação:

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

Um alias também seria bom aqui, mas normalmente não é expandido em um script de shell [ 1 ], e de qualquer maneira os aliases são substituídos pelas funções de shell "para quase todos os fins" [ 2 ], de acordo com a página de manual do Bash .

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

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

Com a --typeopção, você pode "canonizar" configurações específicas como números inteiros, booleanos ou caminhos (expandindo 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

Exemplo rápido e sujo um pouco mais robusto

Disponibilize todas as variáveis mytool.inicomo SECTIONNAME_VARIABLENAMEno ambiente atual, preservando o espaço em branco interno nos valores-chave:

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

O que a expressão sed está fazendo, em inglês, é

  1. encontrar um monte de caracteres não menstruais até um período, lembrando que \1,
  2. encontrar um monte de caracteres até um sinal de igual, lembrando que como \2e
  3. localizando todos os caracteres após o sinal de igual como \3
  4. finalmente, na cadeia de substituição
    • o nome da seção + nome da variável está em maiúsculas e
    • a parte do valor está entre aspas duplas, caso contenha caracteres que tenham um significado especial para o shell se não estiverem entre aspas (como espaço em branco)

As seqüências \Ue \Ena sequência de substituição (que maiúscula essa parte da sequência de substituição) são sedextensão GNU . No macOS e no BSD, você usaria várias -eexpressões para obter o mesmo efeito.

Lidar com aspas incorporadas e espaços em branco nos nomes das seções (o que git configpermite) é deixado como um exercício para o leitor.:)

Usando nomes de seção como chaves em uma matriz associativa Bash

Dado:

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

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

Isso produzirá o resultado solicitado pelo OP, simplesmente reorganizando algumas das capturas na expressão de substituição do sed e funcionará bem sem o GNU sed:

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

Prevejo que pode haver alguns desafios com a citação de um .iniarquivo do mundo real , mas funciona para o exemplo fornecido. Resultado:

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