Como posso analisar um arquivo ini cujos valores podem conter determinados caracteres?

3

Eu analisei alguns scripts de análise e vi este um usado algumas vezes aqui, então estou tentando ver se funcionará para mim. Parece que ele lê o arquivo ini linha por linha várias vezes e a cada passo ele constrói progressivamente uma função que finalmente é avaliada. Funciona bem para alguns caracteres especiais, mas não para outros. Se um valor no arquivo contiver uma aspa simples ou um símbolo maior / menor que, o script retornará erros de sintaxe. Outros símbolos também criam resultados inesperados. Como posso lidar com esses caracteres quando eles são encontrados?

Esta é a função que analisa o ini.

#!/usr/bin/env bash
cfg_parser ()
{
    ini="$(<$1)"                # read the file
    ini="${ini//[/\[}"          # escape [
    ini="${ini//]/\]}"          # escape ]
    IFS=$'\n' && ini=( ${ini} ) # convert to line-array
    ini=( ${ini[*]//;*/} )      # remove comments with ;
    ini=( ${ini[*]/\    =/=} )  # remove tabs before =
    ini=( ${ini[*]/=\   /=} )   # remove tabs be =
    ini=( ${ini[*]/\ =\ /=} )   # remove anything with a space around =
    ini=( ${ini[*]/#\[/\}$'\n'cfg.section.} ) # set section prefix
    ini=( ${ini[*]/%\]/ \(} )    # convert text2function (1)
    ini=( ${ini[*]/=/=\( } )    # convert item to array
    ini=( ${ini[*]/%/ \)} )     # close array parenthesis
    ini=( ${ini[*]/%\ \)/ \} ) # the multiline trick
    ini=( ${ini[*]/%\( \)/\(\) \{} ) # convert text2function (2)
    ini=( ${ini[*]/%\} \)/\}} ) # remove extra parenthesis
    ini[0]="" # remove first element
    ini[${#ini[*]} + 1]='}'    # add the last brace
    eval "$(echo "${ini[*]}")" # eval the result
}

arquivo ini

[Section1]
value1=abc'def # unexpected EOF while looking for matching '''
value2=ghi>jkl # syntax error near unexpected token '>'
value3=mno$pqr # executes ok but outputs "mnoqr"
value4=stu;vwx # executes ok but outputs "stu"
    
por dimmech 21.05.2016 / 02:54

2 respostas

4

O fato de você poder fazer algo em bash não significa que você deva .

Os scripts

sh (e bash etc) são mais adequados para serem wrappers relativamente simples para iniciar programas ou em torno de comandos de processamento de texto. Para tarefas mais complicadas, incluindo a análise de arquivos ini e sua atuação, outras linguagens são mais apropriadas. Você já pensou em escrever seu script em perl ou python ? Ambos têm bons analisadores de arquivo .ini - usei Config::INI do perl várias vezes quando precisei analisar um arquivo ini.

Mas se você insistir em fazer isso no bash, você deve usar um array associativo ao invés de definir variáveis individuais.

Comece com algo assim:

#! /bin/bash

inifile='user1074170.ini' 

# declare $config to be an associative array
declare -A config

while IFS='=' read -r key val ; do 
    config["$key"]="$val"
done <  <(sed -E -e '/^\[/d
                     s/#.*//
                     s/[[:blank:]]+$|^[[:blank:]]+//g' "$inifile" )

# now print out the config array
set | grep '^config='

O script sed exclui a linha [Section1] (na verdade, todas as linhas que começam com um colchete aberto [ - você vai querer lidar com isso de forma diferente [1] em um arquivo ini com várias seções) e remove comentários, bem como espaços em branco iniciais e finais. O loop while lê cada linha, usando = como um delimitador de campo e atribui o conteúdo às variáveis $ key e $ val, que são então adicionadas ao array $ config.

Saída:

config=([value1]="abc\'def" [value3]="mno\$pqr" [value2]="ghi>jkl" [value4]="stu;vwx" )

Você pode usar as entradas da matriz mais tarde no seu script da seguinte forma:

$ echo value1 is "${config[value1]}"
value1 is abc'def

$ [ "${config[value4]}" = 'stu;vwx' ] && echo true
true

[1] awk ou perl tem maneiras fáceis e convenientes de ler arquivos no modo "parágrafo". Um parágrafo sendo definido como um bloco de texto separado de outros blocos de texto por uma ou mais linhas em branco.

por exemplo. para trabalhar somente com [Section1] , insira o script awk abaixo imediatamente antes do script sed ser alimentado no loop while acima:

awk -v RS= -v ORS='\n\n' '/\[Section1\]/' "$inifile" | sed ...

(e remova "$inifile" do final da linha de comando sed , é claro - você não quer alimentar o arquivo novamente depois de ter tido o trabalho de extrair apenas [Section1] dele ).

A configuração de ORS não é estritamente necessária se você estiver extraindo apenas uma seção do arquivo ini - mas será útil manter a separação de parágrafos se você estiver extraindo duas ou mais seções.

    
por 21.05.2016 / 07:41
0

Eu sei que é uma resposta incompleta, mas o MySQL.lns no augeas parece ser capaz de analisar a maior parte disso. Em augtool :

augtool> set /augeas/load/testini/incl "/root/test.ini"
augtool> set /augeas/load/testini/lens "MySQL.lns"
augtool> load
augtool> ls /files/root/
.ssh/      test.ini/
augtool> ls /files/root/test.ini
target/ = Section1
augtool> ls /files/root/test.ini/target/
value1/ = abc'def
value2/ = ghi>jkl
value3/ = mno$pqr
value4/ = stu

O único em que ele estragou é o último e o TBH, não acho que seja um erro. Nos arquivos .ini , o ponto e vírgula marca o início de um comentário. Eu também gostaria de perguntar se seus dados realmente se parecem com isso.

Em caso afirmativo, você pode fazer apenas sed antes de definir ; para algum valor de caractere não utilizado e depois transformá-lo novamente em pós-processamento. No fim das contas, você precisará de alguns padrões, para que o arquivo seja capaz de ter qualquer estrutura discernível.

EDITAR:

Eu testei com a lente PHP e obtive a coisa toda, desde que os valores fossem citados:

[root@vlzoreman ~]# augtool
augtool> set /augeas/load/testini/lens "PHP.lns"
augtool> set /augeas/load/testini/incl "/root/test.ini"
augtool> load
augtool>  ls /files/root/test.ini/Section1/
value1 = abc'def
value2 = ghi>jkl
value3 = mno$pqr
value4 = stu;vwx

Caso contrário, chegou até a lente do MySQL.

EDIT # 2:

Tenho certeza de que há uma maneira mais clara de escrever isso, mas esse é o exemplo:

[root@vlp-foreman ~]# bash bash.sh
Values for: Section1:
        :: value1 is abc'def
        :: value2 is ghi>jkl
        :: value3 is mno$pqr
        :: value4 is stu;vwx
Values for: Section2:
        :: value1 is abc'def

O script é:

#!/bin/bash

sections=$(augtool -A --transform "PHP.lns incl /root/test.ini" ls /files/root/test.ini | cut -f1 -d/)

for currentSection in $sections; do

  echo "Values for: $currentSection:"

  fields=$(augtool -A --transform "PHP.lns incl /root/test.ini" ls /files/root/test.ini/$currentSection | awk '{print $1}')

  for currentField in $fields; do

    currentValue=$(augtool -A --transform "PHP.lns incl /root/test.ini" print /files/root/test.ini/$currentSection/$currentField | cut -f2 -d=)
    currentValue=$(echo $currentValue | sed -e 's/^[ \t]*//' -e 's/[ \t]*$//' | sed -e 's/^"//' -e 's/"$//')

    echo -e "\t:: $currentField is $currentValue"

  done

done
    
por 21.05.2016 / 03:35