正在 bash 中解析 .ini 文件

Parsing .ini file in bash

我有一个下面的属性文件,想按如下所述解析它。请帮忙做这件事。

.ini 我创建的文件:

[Machine1]

app=version1


[Machine2]

app=version1

app=version2

[Machine3]

app=version1
app=version3

我正在寻找一个解决方案,其中 ini 文件应该像

一样被解析
[Machine1]app = version1
[Machine2]app = version1
[Machine2]app = version2
[Machine3]app = version1
[Machine3]app = version3

谢谢。

尝试:

$ awk '/\[/{prefix=[=10=]; next} {print prefix [=10=]}' file.ini
[Machine1]app=version1
[Machine2]app=version1
[Machine2]app=version2
[Machine3]app=version1
[Machine3]app=version3

工作原理

  • /\[/{prefix=[=12=]; next}

    如果任何行以 [ 开头,我们将该行保存在变量 prefix 中,然后跳过其余命令并跳转到 next 行。

  • {print prefix [=16=]}

    如果当前行不为空,我们打印当前行后面的前缀。

添加空格

在出现的任何 = 周围添加空格:

$ awk -F= '/\[/{prefix=[=11=]; next} {=; print prefix [=11=]}' OFS=' = ' file.ini
[Machine1]app = version1
[Machine2]app = version1
[Machine2]app = version2
[Machine3]app = version1
[Machine3]app = version3

这通过使用 = 作为输入的字段分隔符和 = 作为输出的字段分隔符来实现。

您可以尝试使用 awk:

 awk '/\[[^]]*\]/{          # Match pattern like [...]
        a=;next           # store the pattern in a
      } 
      NF{                   # Match non empty line
        gsub("=", " = ")    # Add space around the = character
        print a [=10=]          # print the line
     }' file

我喜欢@John1024 的回答。我正在寻找那个。我创建了一个 bash 函数,允许我根据他的想法查找部分或特定键:

function iniget() {
  if [[ $# -lt 2 || ! -f  ]]; then
    echo "usage: iniget <file> [--list|<section> [key]]"
    return 1
  fi
  local inifile=

  if [ "" == "--list" ]; then
    for section in $(cat $inifile | grep "\[" | sed -e "s#\[##g" | sed -e "s#\]##g"); do
      echo $section
    done
    return 0
  fi

  local section=
  local key
  [ $# -eq 3 ] && key=

  # 
  # This awk line turns ini sections => [section-name]key=value
  local lines=$(awk '/\[/{prefix=[=10=]; next} {print prefix [=10=]}' $inifile)
  for line in $lines; do
    if [[ "$line" = \[$section\]* ]]; then
      local keyval=$(echo $line | sed -e "s/^\[$section\]//")
      if [[ -z "$key" ]]; then
        echo $keyval
      else          
        if [[ "$keyval" = $key=* ]]; then
          echo $(echo $keyval | sed -e "s/^$key=//")
        fi
      fi
    fi
  done
}

所以给出这个 file.ini

[Machine1]
app=version1
[Machine2]
app=version1
app=version2
[Machine3]
app=version1
app=version3

则产生如下结果

$ iniget file.ini --list
Machine1
Machine2
Machine3

$ iniget file.ini Machine3
app=version1
app=version3

$ iniget file.ini Machine1 app
version1

$ iniget file.ini Machine2 app
version2
version3

再次感谢@John1024 的回答,我竭尽全力试图创建一个简单的 bash 支持部分的 ini 解析器。

Tested on Mac using GNU bash, version 5.0.0(1)-release (x86_64-apple-darwin18.2.0)

这里有很好的答案。我对@davfive 的功能进行了一些修改,以使其更适合我的用例。这个版本在很大程度上是相同的,除了它允许在 = 个字符前后有空格,并允许值中有空格。

# Get values from a .ini file
function iniget() {
    if [[ $# -lt 2 || ! -f  ]]; then
        echo "usage: iniget <file> [--list|<section> [key]]"
        return 1
    fi
    local inifile=
    
    if [ "" == "--list" ]; then
        for section in $(cat $inifile | grep "^\s*\[" | sed -e "s#\[##g" | sed -e "s#\]##g"); do
            echo $section
        done
        return 0
    fi
    
    local section=
    local key
    [ $# -eq 3 ] && key=
    
    # This awk line turns ini sections => [section-name]key=value
    local lines=$(awk '/\[/{prefix=[=10=]; next} {print prefix [=10=]}' $inifile)
    lines=$(echo "$lines" | sed -e 's/[[:blank:]]*=[[:blank:]]*/=/g')
    while read -r line ; do
        if [[ "$line" = \[$section\]* ]]; then
            local keyval=$(echo "$line" | sed -e "s/^\[$section\]//")
            if [[ -z "$key" ]]; then
                echo $keyval
            else          
                if [[ "$keyval" = $key=* ]]; then
                    echo $(echo $keyval | sed -e "s/^$key=//")
                fi
            fi
        fi
    done <<<"$lines"
}

为了采用不同的部分并将部分名称(包括 'no-section'/Default 一起)添加到其每个相关关键字(连同 = 及其键值),此 one-liner AWK 可以结合一些 clean-up 正则表达式来解决问题。

ini_buffer="$(echo "$raw_buffer" | awk '/^\[.*\]$/{obj=[=10=]}/=/{print obj [=10=]}')"  

将按照您的要求输出台词:

+++ awk '/^\[.*\]$/{obj=[=11=]}/=/{print obj [=11=]}'
++ ini_buffer='[Machine1]app=version1
[Machine2]app=version1
[Machine2]app=version2
[Machine3]app=version1
[Machine3]app=version3'

INI-format 文件的完整解决方案

正如Clonato, INI-format expert所说,对于最新的 INI 版本 1.4 (2009-10-23),INI 文件还有其他几个棘手的方面:

  1. 节名称的字符集约束
  2. 关键字的字符集约束
  3. 最后是键值能够处理大部分未在部分和关键字名称中使用的内容;这包括在一对相同的 single/double-quote.
  4. 中嵌套引号

除了引号的嵌套,一个INI-format Github解析INI-format文件默认部分的完整解决方案:

# syntax: ini_file_read <raw_buffer>
# outputs: formatted bracket-nested "[section]keyword=keyvalue"
ini_file_read()
{
  local ini_buffer raw_buffer hidden_default
  raw_buffer=""

  # somebody has to remove the 'inline' comment
  # there is a most complex SED solution to nested 
  #    quotes inline comment coming ... TBA
  raw_buffer="$(echo "$raw_buffer" | sed '
  s|[[:blank:]]*//.*||; # remove //comments
  s|[[:blank:]]*#.*||; # remove #comments
  t prune
  b
  :prune
  /./!d; # remove empty lines, but only those that
         # become empty as a result of comment stripping'
 )"

  # awk does the removal of leading and trailing spaces
  ini_buffer="$(echo "$raw_buffer" | awk '/^\[.*\]$/{obj=[=12=]}/=/{print obj [=12=]}')"  # original

  ini_buffer="$(echo "$ini_buffer" | sed  's/^\s*\[\s*/\[/')"
  ini_buffer="$(echo "$ini_buffer" | sed  's/\s*\]\s*/\]/')"

  # finds all 'no-section' and inserts '[Default]'
  hidden_default="$(echo "$ini_buffer" \
              | egrep '^[-0-9A-Za-z_$\.]+=' | sed 's/^/[Default]/')"
  if [ -n "$hidden_default" ]; then
    echo "$hidden_default"
  fi
  # finds sectional and outputs as-is
  echo "$(echo "$ini_buffer" | egrep '^\[\s*[-0-9A-Za-z_$\.]+\s*\]')"
}

此 Whosebug post 的单元测试包含在此文件中:

来源: