当值跨越多行时,如何从文件中提取键值对?

时间:2016-09-09 14:28:48

标签: linux bash shell awk sed

我已经花了几周的时间来进行bash脚本编写,而且我还没有达到足够的进展来解决这个问题。任何帮助将不胜感激!

我有一个“ script.conf ”文件,其中包含以下内容:

key1=value1
key2=${HOME}/Folder
key3=( "k3v1" "k3 v2" "k3v3")
key4=( "k4v1"
    "k4 v2"
    "k4v3"
)
key5=value5
#key6="Do Not Include Me"

在bash脚本中,我想将此script.conf文件的内容读入数组。我已经学会了如何处理键1,2,3和5的场景,但是 key4场景会突破它,并且它跨越多行。

我一直在探索使用sed -n '/=\s*[(]/,/[)]/{/'来捕获key4及其值,但我无法弄清楚如何混合它以便在匹配中捕获其他键。范围语法对我来说也是新的,所以我还没弄清楚如何分离键/值。我觉得有一个简单的正则表达式可以实现我想要的...在纯文本中:“查找并分组模式^(.*)=(对于密钥),然后将所有内容分组到'='char之后直到另一个发现^(.*)=匹配,冲洗并重复“。我想如果我这样做,我需要更改while读取行,以便不为我处理键/值分离(我会在等待响应时查看这个)。顺便说一下,我认为key4的值被平整(删除新行)的解决方案是可以接受的;我知道对于key3我必须将值存储为字符串,然后在我想迭代它时将其转换为数组,因为数组元素显然不能包含列表。

我是sed的正确路径还是awk或其他工具的工作? (我还没敢冒险进入awk)。是否有一种更容易的方法,因为我太深入森林了(比如更改LoadConfigFile函数中的while read行)?

以下是我目前在 script.sh 中处理和捕获其他对到$ config数组中的代码:

__AppDir=$(dirname $0)
__AppName=${__ScriptName%.*}


typeset -A config   #init config array
config=(    #Setting Default Config values
    [key1]="defaultValue1"
    [key2]="${HOME}/defaultFolder"

    [QuietMode]=0
    [Verbose]=0     #Ex. Usage: [[ "${config[Verbose]}" -gt 0 ]] && echo ">>>Debug print"
)

function LoadConfigFile() {
    local cfgFile="${1}"
    shopt -s extglob    #Needed to remove trailing spaces
    if [ -f ${cfgFile} ]; then
        while IFS='=' read -r key value; do
            if [[ "${key:0:1}" == "#" ]]; then
                #echo "Skipping Comment line: ${key}"
            elif [ "${key:-EMPTY}" != "EMPTY" ]; then
                value="${value%%\#*}"   # Delete in-line, right comments
                value="${value%%*( )}"  # Delete trailing spaces
                value="${value%%( )*}"  # Delete leading spaces
                #value="${value%\"*}"   # Delete opening string quotes
                #value="${value#\"*}"   # Delete closing string quotes

                #Manipulate any variables included in the value so that they can be expanded correctly 
                #  - value must be stored in the format: "${var1}".  `backticks`, "$var2", and "doubleQuotes" are left as is
                value="${value//\"/\\\"}"    # Escape double quotes for eval
                value="${value//\`/\\\`}"    # Escape backticks for eval
                value="${value//\$/\\\$}"    # Escape ALL '$' for eval
                value="${value//\\\${/\${}" # Undo the protection of '$' if it was followed by a '{'        
                value=$(eval "printf '%s\n' \"${value}\"")

                config[${key}]=${value} #Store the value into the config array at the specified key                 
                echo "  >>>DBG: Key = ${key}, Value = ${value}"
            #else
            #   echo "Skipped Empty Key"
            fi

        done < "${cfgFile}"
    fi
}

CONFIG_FILE=${__AppDir}/${__AppName}.conf
echo "Config File @ ${CONFIG_FILE}"

LoadConfigFile ${CONFIG_FILE}

#Print elements of $config
echo "Script Config Values:"
echo "----------------------------"
for key in "${!config[@]}"; do      #The '!' char gets an array of the keys, without it, we would get an array of the values
    printf "  %-20s = %s\n" "${key}" "${config[${key}]}"
done
echo "------ End Script Config ------"

#To convert to an array...
declare -a valAsArray=${config[RequiredAppPackages]}    #Convert the value from a string to an array
echo "Count = ${#valAsArray[@]}"
for itemCfg in "${valAsArray[@]}"; do
    echo "  item = ${itemCfg}"
done

正如我之前提到的,我只是开始学习bash和Linux脚本一般,所以如果你看到我在代码的其他方面做了一些禁忌的话,请随时提供反馈意见。评论......我不想在早期开始养成坏习惯: - )。

*如果重要,操作系统是Ubuntu 14.04。

修改 根据要求,在阅读script.conf文件后,我希望$config[@]中的元素等同于以下内容:

typeset -A config   #init config array
config=(    
    [key1]="value1"
    [key2]="${HOME}/Folder"
    [key3]="( \"k3v1\" \"k3 v2\" \"k3v3\" )"
    [key4]="( \"k4v1\" \"k4 v2\" \"k4v3\" )"
    [key5]="value5"
)

我希望能够将元素'key4'和'key3'的值转换为数组,并在以下代码中以相同的方式迭代它们:

declare -a keyValAsArray=${config[keyN]}    #Convert the value from a string to an array
echo "Count = ${#keyValAsArray[@]}"
for item in "${keyValAsArray[@]}"; do
    echo "  item = ${item}"
done

如果为了key4的值而保留\ n,我认为这不重要......这取决于declare是否存在问题。

1 个答案:

答案 0 :(得分:0)

shell是一种环境,可以使用语言调用工具来对这些调用进行排序。它不是操纵文本的工具。用于操作文本的标准UNIX工具是awk。试图操作shell中的文本是一个坏习惯,请参阅why-is-using-a-shell-loop-to-process-text-considered-bad-pr‌​actice了解一些原因

你仍然没有发布填充配置数组的预期结果,所以我不确定,但我认为这是你想要的:

$ cat tst.sh
declare -A config="( $(awk '
    { gsub(/^[[:space:]]+|([[:space:]]+|#.*)$/,"") }
    !NF { next }
    /^[^="]+=/ {
        name = gensub(/=.*/,"",1)
        value = gensub(/^[^=]+=/,"",1)
        n2v[name] = value
        next
    }
    { n2v[name] = n2v[name] OFS $0 }
    END {
        for (name in n2v) {
            value = gensub(/"/,"\\\\&","g",n2v[name])
            printf "[%s]=\"%s\"\n", name, value
        }
    }
    ' script.conf
) )"

declare -p config

$ ./tst.sh
declare -A config='([key5]="value5" [key4]="( \"k4v1\" \"k4 v2\" \"k4v3\" )" [key3]="( \"k3v1\" \"k3 v2\" \"k3v3\")" [key2]="/home/Ed/Folder" [key1]="value1" )'

以上使用GNU awk for gensub(),而其他awks则使用[g] sub()代替。