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

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

Eu olhei alguns scripts de análise do bash ini e viesseum usado algumas vezes aqui, então estou tentando ver se vai funcionar para mim. Parece que ele lê o arquivo ini linha por linha várias vezes e, a cada passagem, 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 aspas simples ou 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 conforme 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"

Responder1

O fato de vocêpodefazer algo bashnão significa que vocêdeve.

sh(e bashetc.) scripts 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 analisar arquivos ini e agir sobre eles, outras linguagens são mais apropriadas. Você já pensou em escrever seu roteiro em perlou python? Ambos têm bons analisadores de arquivos .ini - usei Config::INIo módulo do Perl várias vezes quando precisei analisar um arquivo ini.

Mas se você insiste em fazer isso no bash, você deve usar um array associativo em vez 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 sedscript exclui a [Section1]linha (na verdade, todas as linhas que começam com um colchete aberto [- você desejará lidar com isso de maneira diferente [1] em um arquivo ini com múltiplas seções) e remove comentários, bem como espaços em branco iniciais e finais. O whileloop lê cada linha, usando =como 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 do array posteriormente em seu script assim:

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

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

[1] awkou perlpossui maneiras convenientemente fáceis de ler arquivos no modo "parágrafo". Um parágrafo é definido como um bloco de texto separado de outros blocos de texto por uma ou mais linhas em branco.

por exemplo, para trabalhar apenas com [Section1], insira o awkscript abaixo imediatamente antes de o sedscript alimentar o whileloop acima:

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

(e remova "$inifile"do final da sedlinha de comando, é claro - você não deseja alimentar o arquivo novamente depois de se dar ao trabalho de extrair apenas [Section1]dele).

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

Responder2

Eu sei que é uma resposta incompleta, mas o MySQL.lnsin 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 que estragou foi o último e, TBH, não acho que seja um erro. Nos .iniarquivos, o ponto e vírgula marca o início de um comentário. Também gostaria de perguntar se seus dados realmente são assim.

Se isso acontecer, você pode apenas fazer algo sedantes que defina ;algum valor de caractere não utilizado e depois transformá-lo novamente no pós-processamento. Em última análise, você precisará de alguns padrões para que o arquivo seja capaz de ter qualquer estrutura discernível.

EDITAR:

Eu testei com as lentes do PHP e consegui tudo, 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, foi tão longe quanto as lentes do MySQL.

EDITAR #2:

Tenho certeza de que há uma maneira mais limpa de escrever isso, mas este é um exemplo de uso:

[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 roteiro é:

#!/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

Responder3

dê uma olhada em crudini:https://www.pixelbeat.org/programs/crudini/
no Ubuntu você pode instalá-lo para sudo apt install crudini
ler um valor do arquivo ini, execute:

$ value1=$(crudini --get "crudini.ini" "Section1" "value1")

crudini suporta vários formatos de arquivos ini e lida com caracteres especiais:

crudini.ini

[Section1]
value1=abc`def
value2=ghi>jkl
value3=mno$pqr
value4=stu;vwx

lendo valores

$ for i in {1..4}; do crudini --get "crudini.ini" "Section1" "value$i"; done
abc`def
ghi>jkl
mno$pqr
stu;vwx
$

informação relacionada