Chinaunix首页 | 论坛 | 博客
  • 博客访问: 219562
  • 博文数量: 42
  • 博客积分: 0
  • 博客等级: 民兵
  • 技术积分: 420
  • 用 户 组: 普通用户
  • 注册时间: 2014-03-09 10:55
个人简介

每天改变一点点,生活充满了惊喜。

文章分类

全部博文(42)

文章存档

2016年(8)

2015年(29)

2014年(5)

我的朋友

分类: LINUX

2015-11-11 22:31:05

bash不支持多维数组,低版本的bash不支持关联数组(4.0 以上版本才支持),这对数据的处理带来了复杂性。
本文以 ini 配置文件为例,采用一种变通的方法,来解析 ini,并保存至数组。
用一下 ini 文件为例:
  1. [php]
  2. dir="php"
  3. port=""
  4. need_sync=1

  5. [mysql]
  6. dir="mysql"
  7. port="3306"
  8. need_sync=0
  9. type="server"
第一步,把 文件 解析成下面的格式,以与 Shell 数组的定义方法 array=(elem1 elem2) 相适应。
  1. php
  2. need_sync dir port
  3. 1 "php" ""
  4. mysql
  5. need_sync dir type port
  6. 0 "mysql" "server" "3306"
 解析函数的实现代码:
  1. ##! @TODO : parse ini configuration file
  2. ##! @IN : $1 => ini config file path
  3. ##! @OUT : every ini config section include three lines
  4. ##! first line : section name
  5. ##! second line : config key
  6. ##! third line : config value
  7. function parseIniConfig(){
  8.     local l_config_file="$1"
  9.     if [ -f "$l_config_file" ]
  10.     then
  11.         egrep -v '[ \t]*#' ${l_config_file} |
  12.         awk '{
  13.             if (match($0, /\[.*\]/)){
  14.                 print(substr($0, RSTART+1, RLENGTH-2))
  15.                 while (getline nextLine){
  16.                     if (match(nextLine, /\[.*\]/)){
  17.                         for (key in arrConf){
  18.                             strKey = strKey" "key
  19.                             strValue = strValue" " arrConf[key]
  20.                         }
  21.                         printArr(arrConf)
  22.                         delete arrConf
  23.                         print(substr(nextLine, RSTART+1, RLENGTH-2))
  24.                     } else {
  25.                         if (match(nextLine, /=/)){
  26.                             key = substr(nextLine, 1, RSTART-1)
  27.                             value = substr(nextLine, RSTART+1, length(nextLine)-RSTART)
  28.                             arrConf[key] = value
  29.                         }
  30.                     }
  31.                 }
  32.             }
  33.         } END{printArr(arrConf)}
  34.         function printArr(arrInput, strKey,strValue){
  35.             for (key in arrInput){
  36.                 strKey = strKey" "key
  37.                 strValue = strValue" " arrInput[key]
  38.             }
  39.             print substr(strKey, 2, length(strKey)-1)
  40.             print substr(strValue, 2, length(strValue)-1)
  41.         }'
  42.     else
  43.         log_fatal "${BASH_SOURCE[0]}" "$LINENO" "'file not exist: '${l_config_file}"
  44.         return 1
  45.     fi
  46.     return 0
  47. }

第二步,保存配置信息至数组,思路是,为每一个 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 中配置项名和值,两个数组中同一个索引对应的配置项和值也是对应的。
有了这样的映射关系,就可以访问相应的元素了。
  1. ##! @TODO : iniConfLines2Array
  2. ##! @IN : $1 => ini config file path
  3. ##! @OUT : global array
  4. ##! array ARR_INI_SECTION_NAME
  5. ##! array ARR_INI_SECTION_KEY_$INDEX
  6. ##! array ARR_INI_SECTION_VALUE_$INDEX
  7. function iniConf2Array(){
  8.     local l_ini_file_path="$1"
  9.     if [ -f "$l_ini_file_path" ]
  10.     then
  11.         local l_ini_info="$(parseIniConfig ${l_ini_file_path})"
  12.         # l_section_lineno stand for line number in a section, values in : 1,2,3
  13.         local l_section_lineno=0
  14.         # section count equals to SECTION_INDEX + 1
  15.         INI_SECTION_INDEX=0
  16.         # read section info, and save to array
  17.         while read line
  18.         do
  19.             ((l_section_lineno ++))
  20.             case $l_section_lineno in
  21.                 # first line in a record
  22.                 1) ARR_INI_SECTION_NAME[${INI_SECTION_INDEX}]=${line}
  23.                     ;;
  24.                 # second line in a record
  25.                 2)
  26.                     # for every section, create tow arrays to save key and value for conf info
  27.                     eval "ARR_INI_SECTION_KEY_${INI_SECTION_INDEX}=(${line})"
  28.                     ;;
  29.                 # third line in a record
  30.                 3)
  31.                     # array to save value
  32.                     eval "ARR_INI_SECTION_VALUE_${INI_SECTION_INDEX}=(${line})"
  33.                     # a section record end
  34.                     ((l_section_lineno = 0))
  35.                     ((INI_SECTION_INDEX ++))
  36.                     ;;
  37.             esac
  38.         done <<EOF
  39.         $(echo "${l_ini_info}")
  40. EOF
  41.         # the real max index value
  42.         ((INI_SECTION_INDEX --))
  43.     else
  44.         log_fatal "${BASH_SOURCE[0]}" "$LINENO" "'file not exist: '${l_ini_file_path}"
  45.         return 1
  46.     fi
  47.     return 0
  48. }
执行上面的代码,得到的数组是这样的:
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的函数:
  1. ##! @TODO : get index by value in array
  2. ##! @IN : $1 => array, tranfer values in this format : "${array[*]}"
  3. ##! @IN : $2 =>
  4. ##! @OUT : int array index
  5. function getIndexByValueFromArr(){
  6.     local l_value_list="$1"
  7.     l_arr=($(echo ${l_value_list}))
  8.     local l_value="$2"
  9.     for key in "${!l_arr[@]}"
  10.     do
  11.         if [[ "${l_value}" == "${l_arr[$key]}" ]]
  12.         then
  13.             echo ${key}
  14.             return 0
  15.         fi
  16.     done
  17.     return 1
  18. }
实现获取每个 section 中的 port 值:

  1. iniConf2Array "test.ini"
  2. for ((i = 0; i <= ${INI_SECTION_INDEX}; i++))
  3. do
  4.     section_name=${ARR_INI_SECTION_NAME[$i]}
  5.     eval "arr_values=\${ARR_INI_SECTION_KEY_$i[@]}"
  6.     arr_values=($(echo $arr_values))
  7.     if index=$(getIndexByValueFromArr "${arr_values[*]}" "port")
  8.     then
  9.         eval "config_key=\${ARR_INI_SECTION_KEY_$i[index]}"
  10.         eval "config_value=\${ARR_INI_SECTION_VALUE_$i[index]}"
  11.         echo ${section_name} ${config_key} ${config_value}
  12.     else
  13.         log_fatal "${BASH_SOURCE[0]}" "$LINENO" "port not set in ini file'"
  14.     fi
  15. done
还可以将上面的这段获取配置值的代码封装为函数,方便调用:
  1. ##!@TODO get value by setion name and key in ini file
  2. ##! @IN : $1 => section name in ini file
  3. ##! @IN : $2 => key
  4. ##! @OUT : value
  5. function getValueBySectionNameAndKey(){
  6.     local l_sec_name="$1"
  7.     local l_key="$2"
  8.     local l_sec_index=''
  9.     local l_key_index=''
  10.     local l_value=''
  11.     local l_arr_values=''
  12.     eval "l_arr_values=\${ARR_INI_SECTION_NAME[@]}"
  13.     l_sec_index=$(getIndexByValueFromArr "${l_arr_values[*]}" ${l_sec_name})
  14.     eval "l_arr_values=\${ARR_INI_SECTION_KEY_${l_sec_index}[@]}"
  15.     l_key_index=$(getIndexByValueFromArr "${l_arr_values[*]}" ${l_key})
  16.     eval "l_value=\${ARR_INI_SECTION_VALUE_${l_sec_index}[${l_key_index}]}"
  17.     echo ${l_value}
  18.     return 0
  19. }

执行&输出:
  1. $ sh test.sh
  2. php port
  3. mysql port 3306
当然,如果你的 ini 文件 section name 可以作为变量名的一部分,而且在整个ini 文件中是唯一存在的,就可以这样定义数组:
ARR_INI_SECTION_KEY_${section_name}
这样的好处是,可以减少一次索引。
--------------------------------------------------------------------------------------------------
如果环境中的bash版本支持关联数组(可以使用 echo ${BASH_VERSION} 查看),就可以这样定义 iniConf22Array 函数:
  1. function iniConf2Array(){
  2.     local l_ini_file_path="$1"
  3.     if [ -f "$l_ini_file_path" ]
  4.     then
  5.         local l_ini_info="$(parseIniConfig ${l_ini_file_path})"
  6.         # l_section_lineno stand for line number in a section, values in : 1,2,3
  7.         local l_section_lineno=0
  8.         # read section info, and save to array
  9.         local l_section_name=''
  10.         local l_config_keys=''
  11.         local l_arr_config_value=''
  12.         while read line
  13.         do
  14.             ((l_section_lineno ++))
  15.             case $l_section_lineno in
  16.                 1) l_section_name=${line}
  17.                     ;;
  18.                 2) l_config_keys=${line}
  19.                     ;;
  20.                 3) eval "l_arr_config_value=(${line[@]})"
  21.                     local l_fields_index=0
  22.                     for config_key in ${l_config_keys}
  23.                     do
  24.                         eval "ARR_INI_CONFIG_KV_${l_section_name}[${config_key}]=\${l_arr_config_value[\${l_fields_index}]}"
  25.                         ((l_fields_index ++))
  26.                     done
  27.                     ((l_section_lineno = 0))
  28.                     ;;
  29.             esac
  30.         done <<EOF
  31.         $(echo "${l_ini_info}")
  32. EOF
  33.     else
  34.         log_fatal "${BASH_SOURCE[0]}" "$LINENO" "'file not exist: '${l_ini_file_path}"
  35.         return 1
  36.     fi
  37.     return 0
  38. }
之前没使用过Shell的关联数组,这次特意安装了bash 4.2 版本做测试。经过测试:
关联数组需要显式的用 declare -A 来声明,才能使用。
另外,在函数中定义的关联数组是局部变量,函数调用完毕就失效了。这与我们的需求在全局环境中保存这个数组冲突。

目前我找到的方法是,在全局环境中,首先定义好相应的关联数组。
declare -A ARR_INI_CONFIG_KV_php
declare -A ARR_INI_CONFIG_KV_mysql
然后再调用函数才能把 配置信息保存至全局数组中(这样写的坏处是,没有办法封装成函数,在用到这个功能时,都要在脚本中写一遍上述代码)。
当然,上述代码可以写成,从配置文件中获取section_name,再用循环结构去定义,减少程序的硬编码。

————————————————
End


阅读(2970) | 评论(0) | 转发(0) |
给主人留下些什么吧!~~