我们在日常工作中经常会更新配置文件,相对来说配置文件比较简单,常见的有 “key = value”的模式,以下两个例子,均是实现key/value的更新。
第一个:更新简单的key/value配置文件。
针对类似如下的配置文件:
# Sample Conf# default log filelog = /var/log/sample.log# pid filepid-file = /var/run/sample.pid
函数:updateConfigFileA
参数:
- filePath - 配置文件全路径
- args - 允许同时更新多个 key/value pair,通过map的形式传入
这里使用了 io/ioutil 提供的封装函数读写文件,非常方便。
ioutil.ReadFile 将会一次性的读入所有数据,如果源文件不大的话,没有任何问题,反过来源文件太大的话,将占用很大的内存,特别是并发环境下读取的话,占用的内存就非常巨大了。这时候可以考虑用 io.Copy,它 copy 数据从一个 Reader接口,到Writer接口。io.Copy 使用固定的32KB buffer 来copy 数据,无论源文件多少,每次都是固定的32KB,不会大量的占用内存。我们可以定制专门的 Reader 和 Writer 来处理 copy 的数据。
大部分场景下,配置文件都不是很大,基本可以忽略 ioutil.ReadFile 的性能问题。
具体代码如下,
func updateConfigFileA(filePath string, args map[string]string) error {input, err := ioutil.ReadFile(filePath)if err != nil {return err}lines := strings.Split(string(input), "\n")for k, v := range args {if len(k) == 0 {continue}hit := falsefor i, line := range lines {if strings.HasPrefix(strings.TrimSpace(line), k) {lines[i] = fmt.Sprintf("%s = %s", k, v)hit = true}}if !hit {lines = append(lines, fmt.Sprintf("%s = %s", k, v))}}output := strings.Join(lines, "\n")if err = ioutil.WriteFile(filePath, []byte(output), 0644); err != nil {return err}return nil}
ioutil.WriteFile() 接受三个参数
- 待写入的文件
- 待写入的数据
- 待写入文件的 Permission mode
如果待写入的文件不存在的话,才用到这个参数,用其生成新文件
第二个:更新带有分段的key/value配置文件。
针对类似如下的配置文件:
# Sample Conf[System]# default log filelog = /var/log/sample.log# pid filepid-file = /var/run/sample.pid[Home]address = "City|Street|Home-Block" # this is address# this is phone numberphone = 1234567890# this is emailemail = sample1@sample.comemail = sample2@sample.comemail = sample3@sample.com
函数:updateConfigFileB
参数:
- filePath - 配置文件全路径
- section - 段标识,例如System/Home,不能为空
- key/value - 键值对,不能为空
- op - 操作码,有三个取值,
- 0:更新键值对,针对key唯一的场景,更新其value,例如 Home中 addres 和 phone 字段。
- 1:更新键值对,针对key不唯一的场景,增加键值对,例如 Home 中的 email 字段。
- -1:删除键所对应的字段。(可以改代码变成删除键值对)
- 0:更新键值对,针对key唯一的场景,更新其value,例如 Home中 addres 和 phone 字段。
样例:
删除 System 中的 log 字段
updateConfigFileB(“pathToFile”, “System”, “log”, “”, -1)更新 Home 中的 phone 字段
updateConfigFileB(“pathToFile”, “home”, “phone”, “0987654321”, 0)更新 Home 中的 email 字段,添加一个新的键值对
updateConfigFileB(“pathToFile”, “Home”, “email”, “sample4@sample.com”, 1)增加新的分段 Office,及其字段 address
updateConfigFileB(“pathToFile”, “Office”, “address”, “City|Street|Office-Block”, 0)
具体代码如下,
func updateConfigFileC(filePath string, section, key, value string, op int) error {if len(section) == 0 || len(key) == 0 {return errors.New("any of section and key cannot be none")}section = strings.TrimSpace(section)key = strings.TrimSpace(key)file, err := os.OpenFile(filePath, os.O_RDWR, 0644)if err != nil {return err}defer file.Close()var (buffer bytes.BufferwriteBufferToFile = falsehasSection = falsescanner = bufio.NewScanner(file)preWhiteSpaces = strings.Repeat(" ", 4) // how many white spaces leftkvLine = fmt.Sprintf("%s%s = %s", preWhiteSpaces, key, value)patSection = fmt.Sprintf("^\\s*\\[\\s*(?i:%s)\\s*\\]\\s*#?", section)patKV = fmt.Sprintf("(^\\s*%s\\s*=)\\s*(\\S[^#]*)", key)regKV = regexp.MustCompile(patKV)regSection = regexp.MustCompile(`^\s*\[\s*(\S.*)\s*\]\s*#?`))handler := func(orgLine string) ([]string, bool) {var (opUpd = falsehasUpd = falseisNextSection = falselines = []string{orgLine})for scanner.Scan() {line := scanner.Text()smSection := regSection.FindStringSubmatch(line)if len(smSection) > 1 && !strings.EqualFold(smSection[1], section) {isNextSection = truebreak}switch op {case -1: // delete the pair of key and valueif regKV.FindString(line) != "" {hasUpd = truecontinue}case 0: // updatesmKV := regKV.FindStringSubmatch(line)if len(smKV) > 2 {opUpd = trueif strings.TrimSpace(smKV[2]) != strings.TrimSpace(value) {rLine := regKV.ReplaceAllString(line, "$1 "+value+" ")lines = append(lines, rLine)hasUpd = truecontinue}}case 1: // multiple keyssmKV := regKV.FindStringSubmatch(line)if len(smKV) > 2 {if strings.TrimSpace(smKV[2]) == strings.TrimSpace(value) {opUpd = true}}}lines = append(lines, line)}if !opUpd && op != -1 {lines = append(lines, kvLine)for i := len(lines) - 2; i > 0; i-- {if isNull, _ := regexp.MatchString("^\\s*$", lines[i]); isNull {lines[i], lines[i+1] = lines[i+1], lines[i]continue}break}hasUpd = true}if isNextSection {lines = append(lines, scanner.Text())}return lines, hasUpd}for scanner.Scan() {orgLine := scanner.Text()matched, _ := regexp.MatchString(patSection, orgLine)if matched {var updLines []stringupdLines, writeBufferToFile = handler(orgLine)if updLines != nil && writeBufferToFile {for i := range updLines {buffer.WriteString(updLines[i] + "\n")}}hasSection = truecontinue}buffer.WriteString(orgLine + "\n")}if !hasSection && op != -1 {buffer.WriteString("\n[" + section + "]\n")buffer.WriteString(kvLine + "\n")writeBufferToFile = true}if writeBufferToFile {if _, err := file.Seek(0, 0); err != nil {return err}if err := file.Truncate(0); err != nil {return err}if _, err := file.Write(buffer.Bytes()); err != nil {return err}}return nil}
参考链接:
