背景

无论使用任何编程语言开发应用,都离不开配置数据。配置数据提供的形式有多样,不外乎命令行选项(options)、参数(parameters),环境 变量(env vars)以及配置文件等。Golang也不例外。Golang内置flag标准库,可以用来支持部分命令行选项和参数的解析;Golang通过os包提 供的方法可以获取当前环境变量;但Golang没有规定标准配置文件格式(虽说内置支持xml、json),多通过第三方 包来解决配置文件读取的问题。Golang配置相关的第三方包邮很多,作者在本文中给出的配置方案中就包含了主流的第三方配置数据操作包。

一个良好的应用配置层次应该是这样的:

  1. 程序内内置配置项的初始默认值
  2. 配置文件中的配置项值可以覆盖(override)程序内配置项的默认值。
  3. 命令行选项和参数值具有最高优先级,可以override前两层的配置项值。

下面循序渐进探讨golang程序配置方案。

解析命令行选项和参数

这一节关注golang程序如何访问命令行选项和参数。

golang对访问到命令行参数提供了内建的支持:

//cmdlineargs.go
package main

import (
    //      "fmt"
    "os"
    "path/filepath"
)

func main() {
    println("I am ", os.Args[0])

    baseName := filepath.Base(os.Args[0])
    println("The base name is ", baseName)

    // The length of array a can be discovered using the built-in function len
    println("Argument # is ", len(os.Args))

    // the first command line arguments
    if len(os.Args) > 1 {
        println("The first command line argument: ", os.Args[1])
    }
}

执行结果如下:

$go build cmdlineargs.go
$cmdlineargs test one
I am  cmdlineargs
The base name is  cmdlineargs
Argument # is  3
The first command line argument:  test

对于命令行结构复杂一些的程序,我们最起码要用到golang标准库内置的flag包:

//cmdlineflag.go
package main

import (
    "flag"
    "fmt"
    "os"
    "strconv"
)

var (
    // main operation modes
    write = flag.Bool("w", false, "write result back instead of stdout\\n\\t\\tDefault: No write back")

    // layout control
    tabWidth = flag.Int("tabwidth", 8, "tab width\\n\\t\\tDefault: Standard")

    // debugging
    cpuprofile = flag.String("cpuprofile", "", "write cpu profile to this file\\n\\t\\tDefault: no default")
)

func usage() {
    // Fprintf allows us to print to a specifed file handle or stream
    fmt.Fprintf(os.Stderr, "\\nUsage: %s [flags] file [path ...]\\n\\n",
        "CommandLineFlag") // os.Args[0]
    flag.PrintDefaults()
    os.Exit(0)
}

func main() {
    fmt.Printf("Before parsing the flags\\n")
    fmt.Printf("T: %d\\nW: %s\\nC: '%s'\\n",
        *tabWidth, strconv.FormatBool(*write), *cpuprofile)

    flag.Usage = usage
    flag.Parse()

    // There is also a mandatory non-flag arguments
    if len(flag.Args()) < 1 {
        usage()
    }
   
    fmt.Printf("Testing the flag package\\n")
    fmt.Printf("T: %d\\nW: %s\\nC: '%s'\\n",
        *tabWidth, strconv.FormatBool(*write), *cpuprofile)

    for index, element := range flag.Args() {
        fmt.Printf("I: %d C: '%s'\\n", index, element)
    }
}

这个例子中: - 说明了三种类型标志的用法:Int、String和Bool。 - 说明了每个标志的定义都由类型、命令行选项文本、默认值以及含义解释组成。 - 最后说明了如何处理标志选项(flag option)以及非option参数。

不带参数运行:

$cmdlineflag
Before parsing the flags
T: 8
W: false
C: ''

Usage: CommandLineFlag [flags] file [path ...]

  -cpuprofile="": write cpu profile to this file
        Default: no default
  -tabwidth=8: tab width
        Default: Standard
  -w=false: write result back instead of stdout
        Default: No write back

带命令行标志以及参数运行(一个没有flag,一个有两个flag):

$cmdlineflag aa bb
Before parsing the flags
T: 8
W: false
C: ''
Testing the flag package
T: 8
W: false
C: ''
I: 0 C: 'aa'
I: 1 C: 'bb'

$cmdlineflag -tabwidth=2 -w aa
Before parsing the flags
T: 8
W: false
C: ''
Testing the flag package
T: 2
W: true
C: ''
I: 0 C: 'aa'