如何解析ini文件并将其转换为bash数组变量?


13

我正在尝试将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 }'

另外,另一个问题是,它没有考虑附近的空间=。我认为sed可能更适合此工作,但我不知道如何在中保存和存储节名称的临时变量sed

那么有什么想法怎么做?


如果还有其他有效的方法,也可以随时发布解决方案:)
Flint 2012年


对于简单的解决方案,请检查:如何在Shell脚本中获取INI值?在stackoverflow SE。
kenorb'3

Answers:


10

Gawk接受正则表达式作为字段定界符。以下内容消除了等号周围的空格,但将其保留在该行的其余部分。在值周围添加引号,以便在执行Bash分配时保留这些空格(如果有的话)。我假设节名称将是数字变量,但是如果您使用的是Bash 4,则很容易将其改编为使用关联数组,并将节名称本身作为索引。

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

请注意,由于Bash变量名称不能包含空格,因此您可能还希望进行Khaled显示的空格删除(仅在$ 1和section上)。

另外,如果值包含等号,则此方法将不起作用。

另一种技术是使用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

同样,可以很容易地支持关联数组。


1
非常好的信息,我特别喜欢第二种技术,因为它使用内置的bash函数,而不是依赖于外部命令。
弗林特(Flint)2012年

@TonyBarganski:可以将其修改为一个AWK调用,而不用将其管道传递到另一个调用中。
暂停,直到另行通知。

10

我将使用简单的python脚本执行此作业,因为它已内置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中的实现较短,但是我认为这更易读且易于维护。


3
在4.2之前的bash版本中,有必要在填充之前声明一个关联数组,例如print "declare -A %s" % (sec)
Felix Eve

2
代替evalsource <(cat in.ini | ./ini2arr.py)
已暂停,直到另行通知。

3

如果要消除多余的空间,可以使用内置功能gsub。例如,您可以添加:

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

这将删除所有空格。如果要删除令牌开头或结尾的空格,可以使用

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

很酷的把戏。不知道有这样的内置功能:)
Flint 2012年

0

这是一个纯bash解决方案。

这是chilladx发布的内容的新版本和改进版本:

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

对于一个真正易于遵循最初的例子:你下载之后,只需复制的文件bash-ini-parser,并scripts/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  
}

因为不得不投票chmod 777。虽然充其量是阴暗的做法,但是肯定不需要使ini文件可执行。更好的方法是使用sudo读取文件,而不是弄乱权限。
Richlv

@Richlv好的。我对不赞成投票的解释表示赞赏。但是,这只是其中的一小部分,对于回答整个问题而言意义不大。“答案”是链接:github.com/albfan/bash-ini-parser。您可以建议进行编辑,而不是对整个过程投下反对票,因为已经为可选包装函数添加了标签。
BuvinJ

0

总是假设周围有Python的ConfigParser,可能会构建如下的shell帮助器函数:

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

希望这可以帮助!


0

如果您有可用的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; }

同样,在这里也可以使用别名,但是别名通常不会在Shell脚本中扩展[ 1 ],并且根据Bash 手册页,别名“几乎用于所有目的”都被Shell函数所取代[ 2 ] 。

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.iniSECTIONNAME_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扩展。在macOS和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" )
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.