web-dev-qa-db-ja.com

iniファイルを解析してbash配列変数に変換する方法は?

私はiniファイルをbash配列変数に変換しようとしています。サンプルのiniは次のとおりです。

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

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

これらは次のようになります:

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

等々。

今、このコマンドだけを思いつくことができました

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

また、もう1つの問題は、=の近くのスペースが考慮されないことです。この仕事にはsedのほうが適していると思いますが、sedにセクション名の一時変数を保持して格納する方法がわかりません。

これを行う方法はありますか?

13
Flint

Gawkはフィールド区切り文字として正規表現を受け入れます。次の例では、等号の前後のスペースを削除していますが、残りの行ではスペースを保持しています。引用符が値の周りに追加されるため、Bash割り当てが実行されたときに、それらのスペースがあれば保持されます。セクション名は数値変数であると想定していますが、Bash 4を使用している場合は、これを適応させて、セクション名自体をインデックスとする連想配列を使用するのは簡単です。

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

Bashの変数名にはスペースを含めることができないため、Khaledが表示するスペースの削除($ 1とセクションのみ)も実行できることに注意してください。

また、値に等号が含まれている場合、このメソッドは機能しません。

別のテクニックは、Bash while readループし、declareを使用して、ファイルの読み取り時に割り当てを実行します。これは、ほとんどの悪意のあるコンテンツから安全です。

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

ここでも、連想配列はかなり簡単にサポートできます。

組み込みのpython parser :なので、このジョブには単純な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)

そして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]}

確かに、awkにはより短い実装がありますが、これはより読みやすく、保守しやすいと思います。

11
Michał Šrajer

余分なスペースを削除したい場合は、組み込み関数gsubを使用できます。たとえば、以下を追加できます。

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

これにより、すべてのスペースが削除されます。トークンの最初または最後のスペースを削除したい場合は、

gsub(/^ /, "", $1);
gsub(/ $/, "", $1);
3
Khaled

Gitが利用可能で、キー名にアンダースコアを使用できないという制約があれば、git configを汎用のINIパーサー/エディターとして使用できます。

=の前後のキーと値のペアの解析を処理し、意味のない空白を破棄します。さらに、基本的に無料でコメント(;#の両方)と型強制を取得します。以下に、OPの入力.iniと必要な出力(Bash連想配列)の完全な動作例を含めました。

しかし、このような設定ファイルを与えられた

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

[section2]
    anothersetting = 42

…簡単で汚いソリューションが必要であり、Bashの連想配列の設定を使用するという考えに慣れていない場合は、次のような簡単な方法で解決できます。

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

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

現在の環境にsectionname_variablenameという名前の環境変数を作成します。もちろん、これは、値にピリオドや空白が含まれないことが信頼できる場合にのみ機能します(より堅牢なソリューションについては、以下を参照してください)。

その他の簡単な例

Shell関数を使用して入力を保存することにより、任意の値をフェッチします。

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

ここでもエイリアスは問題ありませんが、通常はシェルスクリプトで展開されません[ 1 ]。とにかく、エイリアスはシェル関数によって置き換えられますほとんどすべての目的、 "[ 2 ]、Bashによると man page

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

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

--typeオプションを使用すると、特定の設定を整数、ブール値、またはパスとして「正規化」できます(自動的に~を展開します)。

myini --get --type=path section1.inputdir  # value '~/some/dir'
# result:
# /home/myuser/some/dir

myini --get --type=bool section1.enablesomeotherfeature  # value 'yes'
# result:
# true

もう少し堅牢で迅速かつ汚い例

mytool.iniのすべての変数を、現在の環境でSECTIONNAME_VARIABLENAMEとして使用できるようにし、キー値の内部空白を保持します。

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

Sed表現が英語で行っていることは、

  1. ピリオドまでの非ピリオド文字の束を見つけ、それを\1として覚えて、
  2. 等号までの一連の文字を見つけ、それを\2として覚えて、
  3. 等号の後のすべての文字を\3として検索します
  4. 最後に、置換文字列
    • セクション名+変数名は大文字、そして
    • 値の部分は二重引用符で囲まれます(引用符で囲まれていない場合(空白など)、シェルにとって特別な意味を持つ文字が含まれている場合)

置換文字列内の\Uおよび\Eシーケンス(置換文字列の一部が大文字)は、GNU sed拡張です。とBSDでは、複数の-e式を使用するだけで同じ効果が得られます。

セクションの名前に埋め込まれた引用符と空白の処理(これはgit configで許可されています)は読者のための演習として残されています。 :)

セクション名をキーとしてBash連想配列に使用する

与えられた:

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

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

これは、sed置換式のキャプチャの一部を再配置するだけで、OPが要求する結果を生成し、GNU sedなしで正常に動作します。

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

実際の.iniファイルを引用すると問題が発生する可能性があると思いますが、これは提供されている例では機能します。結果:

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

常にPythonのConfigParserがあると仮定すると、次のようなシェルヘルパー関数を作成できます。

get_network_value()
{
    cat <<EOF | python
import ConfigParser
config = ConfigParser.ConfigParser()
config.read('network.ini')
print (config.get('$IFACE','$param'))
EOF
}

$IFACEおよび$paramは、それぞれセクションのパラメーターです。

このヘルパーは、次のような呼び出しを許可します。

address=`param=address get_network_value` || exit 1
netmask=`param=netmask get_network_value` || exit 1
gateway=`param=gateway get_network_value` || exit 1

お役に立てれば!

これが純粋なbashソリューションです。

これはchilladxが投稿したものの新しく改善されたバージョンです。

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

非常に簡単に実行できる最初の例の場合:これをダウンロードしたら、ファイルをコピーするだけですbash-ini-parserscripts/file.iniを同じディレクトリにコピーし、次に同じディレクトリに提供した例を使用してクライアントテストスクリプトを作成します。

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

ここに私がbash-ini-parserスクリプトに加えたいくつかのさらなる改善があります...

Windowsの行末とUnixでiniファイルを読み取れるようにしたい場合は、ファイルを読み取るものの直後のcfg_parser関数にこの行を追加します。

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

アクセス許可が制限されているファイルを読み取る場合は、次のオプション機能を追加します。

# 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  
}
0
BuvinJ