`
smz113
  • 浏览: 1788 次
  • 性别: Icon_minigender_1
  • 来自: 上海
最近访客 更多访客>>
社区版块
存档分类
最新评论

使用TCL脚本读取配置文件

    博客分类:
  • 1
阅读更多
文章出处:51testing 作者:叶晖 兰海 发布时间:2006-03-17



摘 要: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 直接修改错误信息变量。否则返回的值是空,并不能确定是配置的值的确是空还是执行中出现了错误。

抛砖引玉,共勉。如有错误,望不吝赐教。

分享到:
评论

相关推荐

Global site tag (gtag.js) - Google Analytics