web-dev-qa-db-ja.com

値に特定の文字が含まれている可能性のあるiniファイルを解析するにはどうすればよいですか?

私はいくつかのbashini解析スクリプトを見て、 this ここで数回使用されているのを見たので、それが私のために機能するかどうかを確認しようとしています。 iniファイルを1行ずつ複数回読み取り、パスごとに徐々に評価される関数を作成しているように見えます。一部の特殊文字では正常に機能しますが、他の文字では機能しません。ファイル内の値に単一引用符が含まれている場合、またはより大記号/より小記号が含まれている場合、スクリプトは構文エラーを返します。他の記号も予期しない結果を引き起こします。遭遇したときにこれらの文字をどのように処理できますか?

これは、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"
3
dimmech

あなたができるbashで何かをするという事実は、あなたがすべきであることを意味しません。

sh(およびbashなど)スクリプトは、プログラムを起動したり、テキスト処理コマンドを囲んだりするための比較的単純なラッパーに最適です。 iniファイルの解析や処理など、より複雑なタスクには、他の言語の方が適しています。スクリプトをPerlまたはpythonで書くことを検討しましたか?どちらにも優れた.iniファイルパーサーがあります。iniファイルを解析する必要があるときに、PerlのConfig::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には、「段落」モードでファイルを読み取るための便利で簡単な方法があります。 1つ以上の空白行で他のテキストブロックから分離されたテキストのブロックとして定義されている段落。

例えば[Section1]のみを操作するには、上記のawkループにフィードするsedスクリプトの直前にwhileスクリプトを挿入します。

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

(もちろん、sedコマンドラインの最後から"$inifile"を削除します。ファイルから[Section1]だけを抽出するという問題が発生した後は、ファイルを再度フィードする必要はありません)。

ORSの設定は、iniファイルから1つのセクションのみを抽出する場合は厳密には必要ありませんが、2つ以上のセクションを抽出する場合は段落の区切りを維持するのに役立ちます。

4
cas

私はそれが不完全な答えであることを知っていますが、augeasのMySQL.lnsはそのほとんどを解析できるようです。 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

それが台無しにした唯一のものは最後のものであり、TBH私はそれがエラーだとは思わない。.iniファイルではセミコロンがコメントの始まりを示している。Iまた、データが実際にそのように見えるかどうかも尋ねたいと思います。

その場合は、その前に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
0
Bratchley