Go 读取INI 文件

Goang,INI

Posted by Cosmo-Ma on June 24, 2018

INI file 简介

NI 文件是 Initialization File 的缩写,即初始化文件,是 Windows 的系统配置文件所采用的存储格式,统管 Windows 的各项配置。INI 文件格式由节(section)和键(key)构成,一般用于操作系统、虚幻游戏引擎、GIT 版本管理中,这种配置文件的文件扩展名为.ini。 如下格式:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
[core]
repositoryformatversion = 0

filemode = false
bare = false
logallrefupdates = true
symlinks = false
ignorecase = true
hideDotFiles = dotGitOnly
[remote "origin"]
url = https://github.com/futurerechx
fetch = +refs/heads/*:refs/remotes/origin/*
[branch "master"]
remote = origin
merge = refs/heads/master

INI 文件的格式

INI 文件由多行文本组成,整个配置由[ ]拆分为多个“段”(section)。每个段中又以=分割为“键”和“值”。 INI 文件以;置于行首视为注释,注释后将不会被处理和识别,如下所示:

1
2
3
4
[sectionl]
key1=value1
;key2=value2
[section2]

从INI 文件中取值的函数

熟悉了 INI 文件的格式后,下面我们创建一个 example.ini 文件,并将从 GIT 版本管理配置文件中截取的一部分内容复制到该文件中。 准备好 example.ini 文件后,下面我们开始尝试读取该 INI 文件,并从文件中获取需要的数据,完整的示例代码如下所示:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
package main
import (
    "bufio"
    "fmt"
    "os"
    "strings"
)
// 根据文件名,段名,键名获取ini的值
func getValue(filename, expectSection, expectKey string) string {
    // 打开文件
    file, err := os.Open(filename)
    // 文件找不到,返回空
    if err != nil {
        return ""
    }
    // 在函数结束时,关闭文件
    defer file.Close()
    // 使用读取器读取文件
    reader := bufio.NewReader(file)
    // 当前读取的段的名字
    var sectionName string
    for {
        // 读取文件的一行
        linestr, err := reader.ReadString('\n')
        if err != nil {
            break
        }
        // 切掉行的左右两边的空白字符
        linestr = strings.TrimSpace(linestr)
        // 忽略空行
        if linestr == "" {
            continue
        }
        // 忽略注释
        if linestr[0] == ';' {
            continue
        }
        // 行首和尾巴分别是方括号的,说明是段标记的起止符
        if linestr[0] == '[' && linestr[len(linestr)-1] == ']' {
            // 将段名取出
            sectionName = linestr[1 : len(linestr)-1]
            // 这个段是希望读取的
        } else if sectionName == expectSection {
            // 切开等号分割的键值对
            pair := strings.Split(linestr, "=")
            // 保证切开只有1个等号分割的简直情况
            if len(pair) == 2 {
                // 去掉键的多余空白字符
                key := strings.TrimSpace(pair[0])
                // 是期望的键
                if key == expectKey {
                    // 返回去掉空白字符的值
                    return strings.TrimSpace(pair[1])
                }
            }
        }
    }
    return ""
}
func main() {
    fmt.Println(getValue("example.ini", "remote \"origin\"", "fetch"))
    fmt.Println(getValue("example.ini", "core", "hideDotFiles"))
}

本例并不是将整个 INI 文件读取保存后再获取需要的字段数据并返回,这里使用 getValue() 函数,每次从指定文件中找到需要的段(Section)及键(Key)对应的值。 参数说明如下。

  • filename:INI 文件的文件名。
  • expectSection:期望读取的段。
  • expectKey:期望读取段中的键。

代码说明如下:

  • 使用 bufio 包提供的 NewReader() 函数,传入文件并构造一个读取器。
  • 提前声明段的名字字符串,方便后面的段和键值读取。
  • 构建一个读取循环,不断地读取文件中的每一行。
  • 使用 reader.ReadString() 从文件中读取字符串,直到碰到\n,也就是行结束。这个函数返回读取到的行字符串(包括\n)和可能的读取错误 err,例如文件读取完毕。
  • ,每一行的文本可能会在标识符两边混杂有空格、回车符、换行符等不可见的空白字符,使用 strings.TrimSpace() 可以去掉这些空白字符。
  • 可能存在空行的情况,继续读取下一行,忽略空行。
  • 当行首的字符为;分号时,表示这一行是注释行,忽略一整行的读取。
  • 当前的段匹配期望的段时,进行后面的解析。
  • 对行内容(linestr)通过 strings.Split() 函数进行切割,INI 的键值对使用=分割,分割后 strings.Split() 函数会返回字符串切片,其类型为 []string。这里只考虑一个=的情况,因此被分割后 strings.Split() 函数返回的字符串切片有 2 个元素。
  • 只考虑切割出 2 个元素的情况。其他情况会被忽略, 键值如没有=、行中多余一个=等情况。
  • pair[0] 表示=左边的键。使用 strings.TrimSpace() 函数去掉空白符,如下图所示。
  • 键值对切割出后,还需要判断键是否为期望的键。
  • 匹配期望的键时,将 pair[1] 中保存的键对应的值经过去掉空白字符处理后作为函数返回值返回。