Как я могу захватить значение INI в сценарии оболочки?


у меня есть параметры.ini-файл, например:

[parameters.ini]
    database_user    = user
    database_version = 20110611142248

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

#!/bin/sh    
# Need to get database version from parameters.ini file to use in script    
php app/console doctrine:migrations:migrate $DATABASE_VERSION

Как бы я это сделал?

23 66

23 ответа:

Как насчет захвата для этой строки, а затем с помощью awk

version=$(awk -F "=" '/database_version/ {print }' parameters.ini)

вы можете использовать собственный синтаксический анализатор bash для интерпретации значений ini, по:

$ source <(grep = file.ini)

пример файла:

[section-a]
  var1=value1
  var2=value2
  IPS=( "1.2.3.4" "1.2.3.5" )

чтобы получить доступ к переменным, вы просто печатаете их:echo $var1. Вы также можете использовать массивы, как показано выше (echo ${IPS[@]}).

если вы хотите только одно значение просто grep для этого:

source <(grep var1 file.ini)

это просто, поскольку вам не нужна никакая внешняя библиотека для анализа данных, но она имеет некоторые недостатки. Для пример:

  • если у вас есть пробелы между = (имя и значение переменной), то вы должны сначала обрезать пробелы, например

    $ source <(grep = file.ini | sed 's/ *= */=/g')
    
  • в поддержку ; комментарии, замените их на #:

    $ sed "s/;/#/g" foo.ini | source /dev/stdin
    
  • разделы не поддерживаются (например, если вы [section-name], то вы должны отфильтровать его, как показано выше, например grep =), то же самое для других неожиданных ошибок.

    Если вам нужно чтобы прочитать конкретное значение в определенном разделе, используйте grep -A,sed,awk или ex).

    например.

    source <(grep = <(grep -A5 '\[section-b\]' file.ini))
    

    Примечание: Где -A5 - это количество строк для чтения в разделе. Заменить source С cat для отладки.

  • если у вас есть какие-либо ошибки разбора, игнорировать их, добавив: 2>/dev/null

Читайте также: как разобрать и конвертировать ini файл в переменные в bash массив? в serverfault SE

Bash не предоставляет парсер для этих файлов, очевидно, вы можете использовать код awk или пару вызовов sed, но если вы bash-priest и не хотите больше ничего использовать, то вы можете попробовать следующий неясный код:

#!/usr/bin/env bash
cfg_parser ()
{
    ini="$(<)"                # 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
}

cfg_writer ()
{
    IFS=' '$'\n'
    fun="$(declare -F)"
    fun="${fun//declare -f/}"
    for f in $fun; do
        [ "${f#cfg.section}" == "${f}" ] && continue
        item="$(declare -f ${f})"
        item="${item##*\{}"
        item="${item%\}}"
        item="${item//=*;/}"
        vars="${item//=*/}"
        eval $f
        echo "[${f#cfg.section.}]"
        for var in $vars; do
            echo $var=\"${!var}\"
        done
    done
}

использование:

# parse the config file called 'myfile.ini', with the following
# contents::
#   [sec2]
#   var2='something'
cfg.parser 'myfile.ini'

# enable section called 'sec2' (in the file [sec2]) for reading
cfg.section.sec2

# read the content of the variable called 'var2' (in the file
# var2=XXX). If your var2 is an array, then you can use
# ${var[index]}
echo "$var2"

bash ini-parser можно найти по адресу сайт блога старой школы DevOps.

просто включите ваш .ini-файл в тело bash:

File пример.ini:

DBNAME=test
DBUSER=scott
DBPASSWORD=tiger

File example.sh

#!/bin/bash
#Including .ini file
. example.ini
#Test
echo "${DBNAME}   ${DBUSER}  ${DBPASSWORD}"

Sed one-liner, который учитывает разделы. Пример файла:

[section1]
param1=123
param2=345
param3=678

[section2]
param1=abc
param2=def
param3=ghi

[section3]
param1=000
param2=111
param3=222

скажем, вы хотите param2 из section2. Выполните следующие действия:

sed -nr "/^\[section2\]/ { :l /^param2[ ]*=/ { s/.*=[ ]*//; p; q;}; n; b l;}" ./file.ini

даст вам

def

одним из более возможных решений

dbver=$(sed -n 's/.*database_version *= *\([^ ]*.*\)//p' < parameters.ini)
echo $dbver

все решения, которые я видел до сих пор, также попали в закомментированные строки. Это не так, если код комментария ;:

awk -F '=' '{if (! ( ~ /^;/) &&  ~ /database_version/) print }' file.ini

отображение значения my_key в ini-стиль my_file:

sed -n -e 's/^\s*my_key\s*=\s*//p' my_file
  • -n -- ничего не печатайте по умолчанию
  • -e -- выполнить выражение
  • s/PATTERN//p -- отображение всего, что следует этой схеме В шаблоне:
  • ^ -- картины начинается в начале строки
  • \s -- пробельный символ
  • * -- ноль или много (пробельные символы)

пример:

$ cat my_file
# Example INI file
something   = foo
my_key      = bar
not_my_key  = baz
my_key_2    = bing

$ sed -n -e 's/^\s*my_key\s*=\s*//p' my_file
bar

так:

найти шаблон, где строка начинается с нуля или много пробелов символов, далее следует строка my_key, затем ноль или много пробелов, знак равенства, затем ноль или много пробелов снова. Отображение остальной части содержимого в этой строке в соответствии с этим шаблоном.

для людей (таких как я), которые хотят читать INI - файлы из сценариев оболочки (read shell, а не bash) - я создал небольшую вспомогательную библиотеку, которая пытается сделать именно это:

https://github.com/wallyhall/shini (лицензия MIT, делайте с ней, как вам угодно. Я связал выше, включая его встроенный, поскольку код довольно длинный.)

Это несколько более сложная, чем просто sed строки, предложенные выше - но работает на очень похожем основа.

функция читает в файле построчно-ищет маркеры раздела ([section]) и "ключ/значение" заявления (key=value).

в конечном итоге вы получаете обратный вызов вашей собственной функции - раздел, ключ и значение.

Вы можете использовать crudini инструмент для получения значений ini, например:

DATABASE_VERSION=$(crudini --get parameters.ini '' database_version)

можно использовать sed чтобы проанализировать файл конфигурации ini, особенно если у вас есть имена разделов, такие как:

# last modified 1 April 2001 by John Doe
[owner]
name=John Doe
organization=Acme Widgets Inc.

[database]
# use IP address in case network name resolution is not working
server=192.0.2.62
port=143
file=payroll.dat

таким образом, вы можете использовать следующее sed скрипт для разбора выше данных:

# Configuration bindings found outside any section are given to
# to the default section.
1 {
  x
  s/^/default/
  x
}

# Lines starting with a #-character are comments.
/#/n

# Sections are unpacked and stored in the hold space.
/\[/ {
  s/\[\(.*\)\]//
  x
  b
}

# Bindings are unpacked and decorated with the section
# they belong to, before being printed.
/=/ {
  s/^[[:space:]]*//
  s/[[:space:]]*=[[:space:]]*/|/
  G
  s/\(.*\)\n\(.*\)/|/
  p
}

это позволит преобразовать исходные данные в плоском формате:

owner|name|John Doe
owner|organization|Acme Widgets Inc.
database|server|192.0.2.62
database|port|143
database|file|payroll.dat

так будет проще разобрать с помощью sed,awk или read, имея имена разделов в каждой строчке.

кредиты и источник:файлы конфигурации для раковины скрипты Майкл Грюневальд

некоторые из ответов не уважают комментарии. Некоторые не уважают разделы. Некоторые распознают только один синтаксис (только ":" или только "="). Некоторые ответы Python терпят неудачу на моей машине из-за различной капитализации или неспособности импортировать модуль sys. Все они слишком лаконичны для меня.

поэтому я написал свой собственный, и если у вас есть современный Python, вы, вероятно, можете назвать это из своей оболочки Bash. Он имеет то преимущество, что придерживается некоторых общих конвенций кодирования Python, и даже предоставляет разумные сообщения об ошибках и помощь. Чтобы использовать его, назовите его что-то вроде myconfig.py (Не называйте это configparser.py или он может попытаться импортировать себя,) сделайте его исполняемым и назовите его как

value=$(myconfig.py something.ini sectionname value)

вот мой код для Python 3.5 на Linux:

#!/usr/bin/env python3
# Last Modified: Thu Aug  3 13:58:50 PDT 2017
"""A program that Bash can call to parse an .ini file"""

import sys
import configparser
import argparse

if __name__ == '__main__':
    parser = argparse.ArgumentParser(description="A program that Bash can call to parse an .ini file")
    parser.add_argument("inifile", help="name of the .ini file")
    parser.add_argument("section", help="name of the section in the .ini file")
    parser.add_argument("itemname", help="name of the desired value")
    args = parser.parse_args()

    config = configparser.ConfigParser()
    config.read(args.inifile)
    print(config.get(args.section, args.itemname))

вот моя версия, которая анализирует разделы и заполняет глобальный ассоциативный массив g_iniProperties С ним. Обратите внимание, что это работает только с Баш В4.2 и выше.

function parseIniFile() { #accepts the name of the file to parse as argument ()
    #declare syntax below (-gA) only works with bash 4.2 and higher
    unset g_iniProperties
    declare -gA g_iniProperties
    currentSection=""
    while read -r line
    do
        if [[ $line = [*  ]] ; then
            if [[ $line = [* ]] ; then 
                currentSection=$(echo $line | sed -e 's/\r//g' | tr -d "[]")  
            fi
        else
            if [[ $line = *=*  ]] ; then
                cleanLine=$(echo $line | sed -e 's/\r//g')
                key=$currentSection.$(echo $cleanLine | awk -F: '{ st = index(,"=");print  substr(,0,st-1)}')
                value=$(echo $cleanLine | awk -F: '{ st = index(,"=");print  substr(,st+1)}')
                g_iniProperties[$key]=$value
            fi
        fi;
    done < 
}

и вот пример кода с использованием функции выше:

parseIniFile "/path/to/myFile.ini"
for key in "${!g_iniProperties[@]}"; do
    echo "Found key/value $key = ${g_iniProperties[$key]}"
done

этот скрипт получит следующие параметры:

это означает, что если ваш ini есть :

pars_ini.ksh

например. как это назвать :


[ среда ]

a=x

[ DataBase_Sector]

DSN = что-то


затем вызов :

pars_ini.ksh / users/bubu_user / parameters.ini DataBase_Sector DSN

Это позволит получить следующее "что-то"

скрипт " pars_ini.КШ" :

\#!/bin/ksh

\#INI_FILE=path/to/file.ini

\#INI_SECTION=TheSection

\# BEGIN parse-ini-file.sh

\# SET UP THE MINIMUM VARS FIRST

alias sed=/usr/local/bin/sed

INI_FILE=

INI_SECTION=

INI_NAME=

INI_VALUE=""


eval `sed -e 's/[[:space:]]*\=[[:space:]]*/=/g' \

    -e 's/;.*$//' \

    -e 's/[[:space:]]*$//' \

    -e 's/^[[:space:]]*//' \

    -e "s/^\(.*\)=\([^\"']*\)$/=\"\"/" \

   < $INI_FILE  \

    | sed -n -e "/^\[$INI_SECTION\]/,/^\s*\[/{/^[^;].*\=.*/p;}"`


TEMP_VALUE=`echo "$"$INI_NAME`

echo `eval echo $TEMP_VALUE`

моя версия однострочного

#!/bin/bash
#Reader for MS Windows 3.1 Ini-files
#Usage: inireader.sh

# e.g.: inireader.sh win.ini ERRORS DISABLE
# would return value "no" from the section of win.ini
#[ERRORS]
#DISABLE=no
INIFILE=
SECTION=
ITEM=
cat $INIFILE | sed -n /^\[$SECTION\]/,/^\[.*\]/p | grep "^[:space:]*$ITEM[:space:]*=" | sed s/.*=[:space:]*//

только что закончил писать свой собственный парсер. Я попытался использовать различные Парсеры, найденные здесь, ни один из них не работает как с ksh93 (AIX), так и с bash (Linux).

Это старый стиль программирования - разбор построчно. Довольно быстро, так как он использовал несколько внешних команд. Немного медленнее из-за всех eval, необходимых для динамического имени массива.

ini поддерживает 3 специальных синтаксиса:

  • includefile=ini-файла --> Загрузите дополнительный ini-файл. Полезно для разделения ini в нескольких файлах, или повторно использовать некоторые части конфигурации
  • includedir=каталог --> То же самое, что и includefile, но включает полный каталог
  • includesection=section --> Скопируйте существующий раздел в текущий раздел.

я использовал весь синтаксис thoses, чтобы иметь довольно сложный, многоразовый ini-файл. Полезно устанавливать продукты при установке новой ОС-мы делаем это много.

значения могут быть доступны с помощью ${ini[$section.$пункт.}] Массив должен быть определены перед вызовом этого.

получать удовольствие. Надеюсь, что это полезно для кого-то другого!

function Show_Debug {
    [[ $DEBUG = YES ]] && echo "DEBUG $@"
    }

function Fatal {
    echo "$@. Script aborted"
    exit 2
    }
#-------------------------------------------------------------------------------
# This function load an ini file in the array "ini"
# The "ini" array must be defined in the calling program (typeset -A ini)
#
# It could be any array name, the default array name is "ini".
#
# There is heavy usage of "eval" since ksh and bash do not support
# reference variable. The name of the ini is passed as variable, and must
# be "eval" at run-time to work. Very specific syntax was used and must be
# understood before making any modifications.
#
# It complexify greatly the program, but add flexibility.
#-------------------------------------------------------------------------------

function Load_Ini {
    Show_Debug "($@)"
    typeset ini_file=""
# Name of the array to fill. By default, it's "ini"
    typeset ini_array_name="${2:-ini}"
    typeset section variable value line my_section file subsection value_array include_directory all_index index sections pre_parse
    typeset LF="
"
    if [[ ! -s $ini_file ]]; then
        Fatal "The ini file is empty or absent in  [$ini_file]"
    fi

    include_directory=$(dirname $ini_file)
    include_directory=${include_directory:-$(pwd)}

    Show_Debug "include_directory=$include_directory"

    section=""
# Since this code support both bash and ksh93, you cannot use
# the syntax "echo xyz|while read line". bash doesn't work like
# that.
# It forces the use of "<<<", introduced in bash and ksh93.

    Show_Debug "Reading file $ini_file and putting the results in array $ini_array_name"
    pre_parse="$(sed 's/^ *//g;s/#.*//g;s/ *$//g' <$ini_file | egrep -v '^$')"
    while read line; do
        if [[ ${line:0:1} = "[" ]]; then # Is the line starting with "["?
# Replace [section_name] to section_name by removing the first and last character
            section="${line:1}"
            section="${section%\]}"
            eval "sections=${$ini_array_name[sections_list]}"
            sections="$sections${sections:+ }$section"
            eval "$ini_array_name[sections_list]=\"$sections\""
            Show_Debug "$ini_array_name[sections_list]=\"$sections\""
            eval "$ini_array_name[$section.exist]=YES"
            Show_Debug "$ini_array_name[$section.exist]='YES'"
        else
            variable=${line%%=*}   # content before the =
            value=${line#*=}       # content after the =

            if [[ $variable = includefile ]]; then
# Include a single file
                Load_Ini "$include_directory/$value" "$ini_array_name"
                continue
            elif [[ $variable = includedir ]]; then
# Include a directory
# If the value doesn't start with a /, add the calculated include_directory
                if [[ $value != /* ]]; then
                    value="$include_directory/$value"
                fi
# go thru each file
                for file in $(ls $value/*.ini 2>/dev/null); do
                    if [[ $file != *.ini ]]; then continue; fi
# Load a single file
                    Load_Ini "$file" "$ini_array_name"
                done
                continue
            elif [[ $variable = includesection ]]; then
# Copy an existing section into the current section
                eval "all_index=\"${!$ini_array_name[@]}\""
# It's not necessarily fast. Need to go thru all the array
                for index in $all_index; do
# Only if it is the requested section
                    if [[ $index = $value.* ]]; then
# Evaluate the subsection [section.subsection] --> subsection
                        subsection=${index#*.}
# Get the current value (source section)
                        eval "value_array=\"${$ini_array_name[$index]}\""
# Assign the value to the current section
# The $value_array must be resolved on the second pass of the eval, so make sure the
# first pass doesn't resolve it ($value_array instead of $value_array).
# It must be evaluated on the second pass in case there is special character like ,
# or ' or " in it (code).
                        eval "$ini_array_name[$section.$subsection]=\"$value_array\""
                        Show_Debug "$ini_array_name[$section.$subsection]=\"$value_array\""
                    fi
                done
            fi

# Add the value to the array
            eval "current_value=\"${$ini_array_name[$section.$variable]}\""
# If there's already something for this field, add it with the current
# content separated by a LF (line_feed)
            new_value="$current_value${current_value:+$LF}$value"
# Assign the content
# The $new_value must be resolved on the second pass of the eval, so make sure the
# first pass doesn't resolve it ($new_value instead of $new_value).
# It must be evaluated on the second pass in case there is special character like ,
# or ' or " in it (code).
            eval "$ini_array_name[$section.$variable]=\"$new_value\""
            Show_Debug "$ini_array_name[$section.$variable]=\"$new_value\""
        fi
    done  <<< "$pre_parse"
    Show_Debug "exit ($@)\n"
    }

эта реализация использует awk и имеет следующие преимущества:

  1. возвращает только первую запись
  2. игнорирует строки, которые начинаются с ;
  3. обрезает начальные и конечные пробелы, но не внутренние пробельные символы

отформатированная версия:

awk -F '=' '/^\s*database_version\s*=/ {
            sub(/^ +/, "", );
            sub(/ +$/, "", );
            print ;
            exit;
          }' parameters.ini

шутка:

awk -F '=' '/^\s*database_version\s*=/ { sub(/^ +/, "", ); sub(/ +$/, "", ); print ; exit; }' parameters.ini

когда я использую пароль в base64, я ставлю разделитель ":" потому что строка base64 может иметь "=". Например (я использую ksh):

> echo "Abc123" | base64
QWJjMTIzCg==

на parameters.ini поставить строку pass:QWJjMTIzCg== и наконец:

> PASS=`awk -F":" '/pass/ {print  }' parameters.ini | base64 --decode`
> echo "$PASS"
Abc123

если в строке есть пробелы типа "pass : QWJjMTIzCg== " добавить | tr -d ' ' обрезать их:

> PASS=`awk -F":" '/pass/ {print  }' parameters.ini | tr -d ' ' | base64 --decode`
> echo "[$PASS]"
[Abc123]

Как и другие ответы Python, вы можете сделать это с помощью -c флаг для выполнения последовательности инструкций Python, заданных в командной строке:

$ python3 -c "import configparser; c = configparser.ConfigParser(); c.read('parameters.ini'); print(c['parameters.ini']['database_version'])"
20110611142248

это имеет то преимущество, что требуется только стандартная библиотека Python и преимущество не писать отдельный файл сценария.

сложная простота

ini file

Я написал быстрый и простой скрипт python для включения в мой скрипт bash.

например, ваш ini-файл называется food.ini а в файле вы можете иметь несколько разделов и несколько строк:

[FRUIT]
Oranges = 14
Apples = 6

скопируйте этот небольшой 6-строчный скрипт Python и сохраните его как configparser.py

#!/usr/bin/python
import configparser
import sys
config = configpParser.ConfigParser()
config.read(sys.argv[1])
print config.get(sys.argv[2],sys.argv[3])

теперь в вашем сценарии bash вы можете сделать это, например.

OrangeQty=$(python configparser.py food.ini FRUIT Oranges)

или

ApplesQty=$(python configparser.py food.ini FRUIT Apples)
echo $ApplesQty

это предполагает:

  1. у Вас установлен Python
  2. у вас установлена библиотека configparser (это должно поставляться с установкой std python)

надеюсь, что это помогает :)

при этом используется системный perl и чистые регулярные выражения:

cat parameters.ini | perl -0777ne 'print "" if /\[\s*parameters\.ini\s*\][\s\S]*?\sdatabase_version\s*=\s*(.*)/'

ответ "Карен Габриелян" среди других ответов был лучшим, но в некоторых средах у нас нет awk, как типичный busybox, я изменил ответ ниже кода.

trim()
{
    local trimmed=""

    # Strip leading space.
    trimmed="${trimmed## }"
    # Strip trailing space.
    trimmed="${trimmed%% }"

    echo "$trimmed"
}


  function parseIniFile() { #accepts the name of the file to parse as argument ()
        #declare syntax below (-gA) only works with bash 4.2 and higher
        unset g_iniProperties
        declare -gA g_iniProperties
        currentSection=""
        while read -r line
        do
            if [[ $line = [*  ]] ; then
                if [[ $line = [* ]] ; then 
                    currentSection=$(echo $line | sed -e 's/\r//g' | tr -d "[]")  
                fi
            else
                if [[ $line = *=*  ]] ; then
                    cleanLine=$(echo $line | sed -e 's/\r//g')
                    key=$(trim $currentSection.$(echo $cleanLine | cut -d'=' -f1'))
                    value=$(trim $(echo $cleanLine | cut -d'=' -f2))
                    g_iniProperties[$key]=$value
                fi
            fi;
        done < 
    }