unix下使用TCL脚本读取配置文件

原贴:http://www.testage.net/AutoTest/Others/200610/1031.htm

使用TCL脚本读取配置文件
作者:未知    文章来源:转自51    点击数:     更新时间:2006-10-20    

摘 要unix下使用TCL脚本读取配置文件;错误处理.

关键词 TCL 、配置文件、unix
 
一.应用范围
在实际工作中,TCL脚本对于一些简单的工作裨益甚大。通常编写脚本都有一定的模式,首先从配置文件读入配置项,初始化变量,然后进行处理,最后输出,在程序的运行过程中需要把一些信息写入日志文件,而调试信息写入日志文件和直接输出到屏幕都可以。
使用任何一种脚本,通常也都会根据实际情况建立起自己的函数库,而这个函数库中对配置文件配置项的读取无疑是非常基本而重要的。
这篇文章的本意是引导刚接触TCL脚本的朋友尽快上手,所以有些细节的说明文字比较细。
 
二.程序讲解
构建一个配置文件,尽可能的接近实际应用:
文件名:config.ini
文件内容:
        #
# 这是注释行
#
 
[]
key1=value1          # 注释
 
[section1]
key2=value2
 
[section2]
key1=value2
 
[section1]
key1=xxxxxx         # 这是最后的返回
 
[section1]
key1=value2
 
 
现在开始对程序的说明:
 
;#-------------------------------------------------------------------------
;# 功能:从配置文件中读取配置项
;# 输入:1. configFile :配置文件名称
;#        2. Section : 段名称
;#        3. Key : 关键字
;#        4. Comment : 注释符,缺省为井号
;#        5. Equal : 关键字和值的分隔符,缺省为等号
;# 输出:1. Value : 相应的值
;#-------------------------------------------------------------------------
 
1. 过程的定义:
   格式: proc name args body
   要点:
Ø         参数列表使用花括号引起;
Ø         变量没有类型;
Ø         变量之间使用空格间隔;
Ø         如果参数有缺省值,使用花括号引起,并赋值
proc getConfig { configFile Section Key {Comment "#"} {Equal "="}} {
       set Value ""                   ;# 记录过程返回的值   
       set FindSection 0              ;# 记录是否找到了section
 
2 .错误的处理
       格式: catch script ?varName?
       功能:执行 script ,如果成功返回 TCL_OK(0) ,否则返回 TCL_ERROR(1) ,提示结果存在 varName 中。比如下面如果打开文件成功, errMsg 返回的就是类似 file3 这样的字符串,此时 err 返回的是 TCL_OK ,就是零;如果文件不存在, errMsg 返回的类似: couldn't open "config1.ini": no such file or directory ,此时 err 返回 TCL_ERROR ,就是一。
 
3 .文件的读写
       格式: openfile fileName ? access ? permission
       讲解:
Ø         fileName 是文件名称;
Ø         access 是存取模式,可以为 r, r+, w, w+, a, a+ 六种模式, r r+ a 三种模式文件必须已经存在,其他三种模式文件不存在就创建一个。本文用的 r 模式,所以文件不存在会提示错误,而不是自动建立一个;
Ø         permission 是权限,举例说明:
有权限为: 000 000 000 :第一组三位为 user 权限;第二组三位为同组其他用户的权限;第三组三位为其他组所有人的权限。每个三位的权限依次代表读,写,执行。如果有相应的权限就设置为一,没有设置为 0 。然后三位为组转成十进制数。
Ø         文件打开后就可以使用其文件 id ,使用完后记得关闭文件
 
       ;# 打开配置文件
       set err [catch {set fileid [open $configFile r]} errMsg]
       if {$err == 1} {
              puts "errMsg : $errMsg"
              return $Value
       }
 
       ;# 成功打开文件后, 一行一行的加以分析
       set rowid 0                       ;#记录当前行数,程序调试时打印调试信息使用的
       seek $fileid 0 start                        ;# 定位到文件头
       while {[eof $fileid] != 1} {                 ;# 读取文件内容
 
4 .变量的自动增长使用命令 incr ,也可以使用 set rowid [expr $rowid + 1] ,显然前者更简捷
              incr rowid                          ;# 记录行数, 从一开始
      
              ;# 读出一行
              gets $fileid line
             
5 .先期处理行,因为注释有两种情况,行中的注释和整行注释,先去掉注释,然后去左右空格就可以得到真正需要的内容;如果先去空格,再去注释,由于行中的注释和内容之间有空格,这样最后得到的内容在去掉行中注释后,会在后面留下一些空格。所以需要先去掉注释,后去掉空格。
 
6 .得到一个字符串在另一个字符串中首先出现的位置,使用函数 string first
       格式: string first string1 string2
       讲解:返回 string1 string2 中第一次出现的位置;如果 string1 不在 string2 中,返回 -1
 
7 .下面有一个细节是初学者经常犯错的地方,那就是: } else { ,这里必须严格的 花括号,空格, else ,空格,花括号,不能把花括号写到上一行或者下一行。
 
8 .返回一个字符串的子串使用 string range 函数
       格式: string range string1 frompos topos
       讲解:取 string1 的从 frompos topos 之间的字符串,注意这里字符串下标是从零开始的,所以用 string length string1 得到的字符串的长度比字符串最后一个字符的位置值要大一。如果取一个字符串中的某个字符就可以使用函数 string index string1 pos
 
9 expr unix shell 中一样,是进行数学运算的函数。
 
              ;# 先去掉注释, 再去掉两端的空格
              set commentpos [string first $Comment $line]        ;# 得到注释符号的位置
              if { $commentpos == 0 } {
                     ;# 行以注释符号开头,忽略掉该行
              } else {
                     if { $commentpos != -1 } {        ;# 行中有注释符号,去掉注释
                            set line [string range $line 0 [expr $commentpos-1]]
                     }
                    
                     set line [string trim $line]          ;# 去掉两端的空格
                     ;# puts "$rowid : line : $line"
                    
10 .在 tcl 脚本中,循环中有 break 命令跳出循环, continue 跳到循环的开头。不过因为括号的使用,其实这里写不写 continue 都没有问题。
 
                     ;# 如果是空就继续循环
                     if { $line == "" } {
                            ;# 回循环
                            continue
                     } else {
                            ;# 先找section
                            set linelen [string length $line]         ;#字符串长度
                            set lastpos [expr $linelen - 1 ]          ;#字符串最后的位置
                            ;# puts "$rowid :len: $linelen   lastpos: $lastpos"
                            if { [string index $line 0] == "/[" && [string index $line $lastpos] == "/]" } {
                                   ;# 如果是查找的section, 修改标志位;如果不是相应的section, 需要将标志重新赋值
                                   if { [string range $line 1 [expr $lastpos - 1 ]] == $Section } {  
                                          ;# puts "$rowid: find section : $Section"
                                          set FindSection 1
                                   } else {
                                          set FindSection 0
                                   }
                            } else {
                                   ;# 已经找到了section才继续找key
                                   if { $FindSection == 1 } {
                                          set equalpos [string first $Equal $line]   ;# 得到等号的位置
                                          if { $equalpos != -1} {
                                                 ;# 如果就是找寻的key,结束循环
                                                 if { [string range $line 0 [expr $equalpos - 1]] == $Key } {
                                                        puts "$rowid: find key"
                                                        set Value [string range $line [expr $equalpos + 1] [string length $line]]
                                                        puts "$rowid: find value: $Value"
                                                        break
                                                 }
                                          } else {
                                                 ;# 回循环
                                          }
                                   } else {
                                          ;# 回循环
                                   }
                            }
                     }
              }
       }
 
       ;# 关闭文件
       close $fileid
      
       return $Value
}
 
 
set val ""
 
;# 测试正常情况下
set val [getConfig "config.ini" "section1" "key1"]
puts "val : $val"
 
;# 测试文件不存在的情况下
set val [getConfig "config1.ini" "section1" "key1"]
puts "val : $val"
 
 
该程序在unix 环境下调试通过。
 
三.伪代码
为了便于理解程序,特写了下面的伪代码:
 
打开配置文件
       IF 出错 THEN
          过程结束
       END IF
 
文件打开成功,定位到文件头
WHILE 没有到文件尾
       读出一行
 
       处理得到的行,先去掉行中的注释,再去掉行左右的空格
      
       IF 行为空 THEN
       ELSE
              行不为空
              IF [开头 ]结束 THEN
                     得到SECTION
                     IF 得到的SECTION就是要找的SECTION THEN
                            置标志位
                     ELSE
                            清空标志位
                     END IF
              ELSE
                     IF 已经找到SECTION了 THEN
                            IF 行中有等号 THEN
                                   IF 等号左边的是在查找的KEY THEN
                                          取得等号右边的值,跳出循环
                                   ELSE
                                          等号左边不是在查找的KEY,继续下一行
                                   END IF
                            ELSE
                                   行中没有等号,继续下一行
                            END IF
                     ELSE
                            还没有找到SECTION,继续下一行
                     END IF
              END IF
       END IF
 
关闭文件
返回值
 
                           
 
四.讨论
这个程序本身比较简单,主要目的是想通过这个程序让tcl初学者对tcl有个感性认识。真正在工作使用中,这个脚本需要改进的地方还有很多。
比如配置文件比较小的话,可以读一个配置项打开一次文件。当配置文件大了以后,这种方法显然效率比较低,可能需要一次将文件的内容全部读入一个列表中,然后进行读取,这样速度会有提高。再比如错误处理只是在过程中将错误信息显示到屏幕上,是不是应该定义一个错误信息的全局变量来传出过程,或者使用upvar 直接修改错误信息变量。否则返回的值是空,并不能确定是配置的值的确是空还是执行中出现了错误。
v

你可能感兴趣的:(bash和正则)