bash不支持多维数组,低版本的bash不支持关联数组(4.0 以上版本才支持),这对数据的处理带来了复杂性。
本文以 ini 配置文件为例,采用一种变通的方法,来解析 ini,并保存至数组。
用一下 ini 文件为例:
-
[php]
-
dir="php"
-
port=""
-
need_sync=1
-
-
[mysql]
-
dir="mysql"
-
port="3306"
-
need_sync=0
-
type="server"
第一步,把 文件 解析成下面的格式,以与 Shell 数组的定义方法 array=(elem1 elem2) 相适应。
-
php
-
need_sync dir port
-
1 "php" ""
-
mysql
-
need_sync dir type port
-
0 "mysql" "server" "3306"
解析函数的实现代码:
-
##! @TODO : parse ini configuration file
-
##! @IN : $1 => ini config file path
-
##! @OUT : every ini config section include three lines
-
##! first line : section name
-
##! second line : config key
-
##! third line : config value
-
function parseIniConfig(){
-
local l_config_file="$1"
-
if [ -f "$l_config_file" ]
-
then
-
egrep -v '[ \t]*#' ${l_config_file} |
-
awk '{
-
if (match($0, /\[.*\]/)){
-
print(substr($0, RSTART+1, RLENGTH-2))
-
while (getline nextLine){
-
if (match(nextLine, /\[.*\]/)){
-
for (key in arrConf){
-
strKey = strKey" "key
-
strValue = strValue" " arrConf[key]
-
}
-
printArr(arrConf)
-
delete arrConf
-
print(substr(nextLine, RSTART+1, RLENGTH-2))
-
} else {
-
if (match(nextLine, /=/)){
-
key = substr(nextLine, 1, RSTART-1)
-
value = substr(nextLine, RSTART+1, length(nextLine)-RSTART)
-
arrConf[key] = value
-
}
-
}
-
}
-
}
-
} END{printArr(arrConf)}
-
function printArr(arrInput, strKey,strValue){
-
for (key in arrInput){
-
strKey = strKey" "key
-
strValue = strValue" " arrInput[key]
-
}
-
print substr(strKey, 2, length(strKey)-1)
-
print substr(strValue, 2, length(strValue)-1)
-
}'
-
else
-
log_fatal "${BASH_SOURCE[0]}" "$LINENO" "'file not exist: '${l_config_file}"
-
return 1
-
fi
-
return 0
-
}
第二步,保存配置信息至数组,思路是,为每一个 ini 文件中的 section 分配一个 index(我们就采用从0开始,按序分配)。
然后 把所有 section 名 保存在一个数组(代码中的 ARR_INI_SECTION_NAME)中,数组元素的key为index值,value为 section 名。
然后为每一个section 分配两个数组(ARR_INI_SECTION_KEY_${INI_SECTION_INDEX}、ARR_INI_SECTION_VALUE_${INI_SECTION_INDEX}),
数组名后缀为 index,分别保存 section 中配置项名和值,两个数组中同一个索引对应的配置项和值也是对应的。
有了这样的映射关系,就可以访问相应的元素了。
-
##! @TODO : iniConfLines2Array
-
##! @IN : $1 => ini config file path
-
##! @OUT : global array
-
##! array ARR_INI_SECTION_NAME
-
##! array ARR_INI_SECTION_KEY_$INDEX
-
##! array ARR_INI_SECTION_VALUE_$INDEX
-
function iniConf2Array(){
-
local l_ini_file_path="$1"
-
if [ -f "$l_ini_file_path" ]
-
then
-
local l_ini_info="$(parseIniConfig ${l_ini_file_path})"
-
# l_section_lineno stand for line number in a section, values in : 1,2,3
-
local l_section_lineno=0
-
# section count equals to SECTION_INDEX + 1
-
INI_SECTION_INDEX=0
-
# read section info, and save to array
-
while read line
-
do
-
((l_section_lineno ++))
-
case $l_section_lineno in
-
# first line in a record
-
1) ARR_INI_SECTION_NAME[${INI_SECTION_INDEX}]=${line}
-
;;
-
# second line in a record
-
2)
-
# for every section, create tow arrays to save key and value for conf info
-
eval "ARR_INI_SECTION_KEY_${INI_SECTION_INDEX}=(${line})"
-
;;
-
# third line in a record
-
3)
-
# array to save value
-
eval "ARR_INI_SECTION_VALUE_${INI_SECTION_INDEX}=(${line})"
-
# a section record end
-
((l_section_lineno = 0))
-
((INI_SECTION_INDEX ++))
-
;;
-
esac
-
done <<EOF
-
$(echo "${l_ini_info}")
-
EOF
-
# the real max index value
-
((INI_SECTION_INDEX --))
-
else
-
log_fatal "${BASH_SOURCE[0]}" "$LINENO" "'file not exist: '${l_ini_file_path}"
-
return 1
-
fi
-
return 0
-
}
执行上面的代码,得到的数组是这样的:
ARR_INI_SECTION_KEY_0=([0]="need_sync" [1]="dir" [2]="port")
ARR_INI_SECTION_KEY_1=([0]="need_sync" [1]="dir" [2]="type" [3]="port")
ARR_INI_SECTION_NAME=([0]="php" [1]="mysql")
ARR_INI_SECTION_VALUE_0=([0]="1" [1]="php" [2]="")
ARR_INI_SECTION_VALUE_1=([0]="0" [1]="mysql" [2]="server" [3]="3306")
INI_SECTION_INDEX 记录section的最大索引值,方便后续遍历。
注意以上变量都需要定义为全局变量(Shell函数中的return返回机制是不能返回数组类型的),以可以让脚本中其它代码访问。
最后我们来看下,怎么去访问?总体思路就是,先反向查找响应的 index,再从相应的数组中,找到需要的值。
因此,先实现一个反向查找index的函数:
-
##! @TODO : get index by value in array
-
##! @IN : $1 => array, tranfer values in this format : "${array[*]}"
-
##! @IN : $2 =>
-
##! @OUT : int array index
-
function getIndexByValueFromArr(){
-
local l_value_list="$1"
-
l_arr=($(echo ${l_value_list}))
-
local l_value="$2"
-
for key in "${!l_arr[@]}"
-
do
-
if [[ "${l_value}" == "${l_arr[$key]}" ]]
-
then
-
echo ${key}
-
return 0
-
fi
-
done
-
return 1
-
}
实现获取每个 section 中的 port 值:
-
iniConf2Array "test.ini"
-
for ((i = 0; i <= ${INI_SECTION_INDEX}; i++))
-
do
-
section_name=${ARR_INI_SECTION_NAME[$i]}
-
eval "arr_values=\${ARR_INI_SECTION_KEY_$i[@]}"
-
arr_values=($(echo $arr_values))
-
if index=$(getIndexByValueFromArr "${arr_values[*]}" "port")
-
then
-
eval "config_key=\${ARR_INI_SECTION_KEY_$i[index]}"
-
eval "config_value=\${ARR_INI_SECTION_VALUE_$i[index]}"
-
echo ${section_name} ${config_key} ${config_value}
-
else
-
log_fatal "${BASH_SOURCE[0]}" "$LINENO" "port not set in ini file'"
-
fi
-
done
还可以将上面的这段获取配置值的代码封装为函数,方便调用:
-
##!@TODO get value by setion name and key in ini file
-
##! @IN : $1 => section name in ini file
-
##! @IN : $2 => key
-
##! @OUT : value
-
function getValueBySectionNameAndKey(){
-
local l_sec_name="$1"
-
local l_key="$2"
-
local l_sec_index=''
-
local l_key_index=''
-
local l_value=''
-
local l_arr_values=''
-
eval "l_arr_values=\${ARR_INI_SECTION_NAME[@]}"
-
l_sec_index=$(getIndexByValueFromArr "${l_arr_values[*]}" ${l_sec_name})
-
eval "l_arr_values=\${ARR_INI_SECTION_KEY_${l_sec_index}[@]}"
-
l_key_index=$(getIndexByValueFromArr "${l_arr_values[*]}" ${l_key})
-
eval "l_value=\${ARR_INI_SECTION_VALUE_${l_sec_index}[${l_key_index}]}"
-
echo ${l_value}
-
return 0
-
}
执行&输出:
-
$ sh test.sh
-
php port
-
mysql port 3306
当然,如果你的 ini 文件 section name 可以作为变量名的一部分,而且在整个ini 文件中是唯一存在的,就可以这样定义数组:
ARR_INI_SECTION_KEY_${section_name}
这样的好处是,可以减少一次索引。
--------------------------------------------------------------------------------------------------
如果环境中的bash版本支持关联数组(可以使用 echo ${BASH_VERSION} 查看),就可以这样定义 iniConf22Array 函数:
-
function iniConf2Array(){
-
local l_ini_file_path="$1"
-
if [ -f "$l_ini_file_path" ]
-
then
-
local l_ini_info="$(parseIniConfig ${l_ini_file_path})"
-
# l_section_lineno stand for line number in a section, values in : 1,2,3
-
local l_section_lineno=0
-
# read section info, and save to array
-
local l_section_name=''
-
local l_config_keys=''
-
local l_arr_config_value=''
-
while read line
-
do
-
((l_section_lineno ++))
-
case $l_section_lineno in
-
1) l_section_name=${line}
-
;;
-
2) l_config_keys=${line}
-
;;
-
3) eval "l_arr_config_value=(${line[@]})"
-
local l_fields_index=0
-
for config_key in ${l_config_keys}
-
do
-
eval "ARR_INI_CONFIG_KV_${l_section_name}[${config_key}]=\${l_arr_config_value[\${l_fields_index}]}"
-
((l_fields_index ++))
-
done
-
((l_section_lineno = 0))
-
;;
-
esac
-
done <<EOF
-
$(echo "${l_ini_info}")
-
EOF
-
else
-
log_fatal "${BASH_SOURCE[0]}" "$LINENO" "'file not exist: '${l_ini_file_path}"
-
return 1
-
fi
-
return 0
-
}
之前没使用过Shell的关联数组,这次特意安装了bash 4.2 版本做测试。经过测试:
关联数组需要显式的用 declare -A 来声明,才能使用。
另外,在函数中定义的关联数组是局部变量,函数调用完毕就失效了。这与我们的需求在全局环境中保存这个数组冲突。
目前我找到的方法是,在全局环境中,首先定义好相应的关联数组。
declare -A ARR_INI_CONFIG_KV_php
declare -A ARR_INI_CONFIG_KV_mysql
然后再调用函数才能把 配置信息保存至全局数组中(这样写的坏处是,没有办法封装成函数,在用到这个功能时,都要在脚本中写一遍上述代码)。
当然,上述代码可以写成,从配置文件中获取section_name,再用循环结构去定义,减少程序的硬编码。
————————————————
End
阅读(3009) | 评论(0) | 转发(0) |