Как проанализировать INI-файл, значения которого могут содержать определенные символы?

Как проанализировать INI-файл, значения которого могут содержать определенные символы?

Я посмотрел пару скриптов парсинга bash ini и увиделэтотone используется здесь несколько раз, поэтому я пытаюсь посмотреть, сработает ли это для меня. Похоже, что он считывает ini-файл построчно несколько раз и с каждым проходом постепенно создает функцию, которая в конечном итоге вычисляется. Он отлично работает для некоторых специальных символов, но не для других. Если значение в файле содержит одинарную кавычку или символ больше/меньше, скрипт возвращает синтаксические ошибки. Другие символы также создают неожиданные результаты. Как мне обрабатывать эти символы, когда они встречаются?

Это функция, которая анализирует 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
}

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"

решение1

Тот факт, что выможетсделать что-то bashне означает, что выдолжен.

shbashт. д.) скрипты лучше всего подходят для относительно простых оболочек для запуска программ или команд обработки текста. Для более сложных задач, включая разбор ini-файлов и выполнение действий с ними, больше подойдут другие языки. Вы рассматривали возможность написания своего скрипта на perlили python? У обоих есть хорошие парсеры .ini-файлов - я несколько раз использовал модуль perl, Config::INIкогда мне нужно было разобрать ini-файл.

Но если вы все же хотите сделать это в bash, вам следует использовать ассоциативный массив вместо установки отдельных переменных.

Начните примерно с этого:

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

Скрипт sedудаляет [Section1]строку (на самом деле, все строки, начинающиеся с открывающей квадратной скобки [— вам нужно будет обрабатывать это по-другому [1] в ini-файле с несколькими разделами), удаляет комментарии, а также начальные и конечные пробелы. Цикл whileсчитывает каждую строку, используя =в качестве разделителя полей, и присваивает содержимое переменным $key и $val, которые затем добавляются в массив $config.

Выход:

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

Вы можете использовать записи массива позже в своем скрипте следующим образом:

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

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

[1] awkили perlиметь удобные простые способы чтения файлов в режиме «абзац». Абзац определяется как блок текста, отделенный от других текстовых блоков одной или несколькими пустыми строками.

например, чтобы работать только с [Section1], вставьте awkскрипт ниже непосредственно перед sedскриптом, входящим в whileцикл выше:

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

(и, конечно, удалите "$inifile"из конца командной sedстроки — вы же не хотите снова передавать файл после того, как вы потрудились извлечь данные только [Section1]из него).

Настройка ORSне является строго обязательной, если вы извлекаете только один раздел из INI-файла, но будет полезна для сохранения разделения абзацев, если вы извлекаете два или более разделов.

решение2

Я знаю, что это неполный ответ, но, MySQL.lnsпохоже, augeas способен разобрать большую часть этого. В 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

Единственное, в чем он ошибся, это последний, и, честно говоря, я не думаю, что это ошибка. В .iniфайлах точка с запятой отмечает начало комментария. Я также хотел бы спросить, действительно ли ваши данные выглядят так.

Если это так, вы можете просто сделать что-то sedдо этого, что устанавливает ;какое-то неиспользуемое значение символа, а затем преобразовать его обратно постобработкой. В конечном счете, вам понадобятся некоторые стандарты, чтобы файл мог иметь какую-либо различимую структуру.

РЕДАКТИРОВАТЬ:

Я протестировал это с помощью PHP-линзы и получил все, пока значения были заключены в кавычки:

[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

В противном случае дело дошло бы до линзы MySQL.

ПРАВКА №2:

Я уверен, что есть более понятный способ записать это, но вот пример использования:

[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

Сценарий:

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

решение3

Взгляни на crudini:https://www.pixelbeat.org/programs/crudini/
в Ubuntu вы можете установить его с помощью sudo apt install crudini
для считывания значения из INI-файла, запустите:

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

crudini поддерживает несколько форматов INI-файлов и обрабатывает специальные символы:

crudini.ini

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

чтение значений

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

Связанный контент