缘起

我一直在想,有什么方式可以让人比较轻易地保持每日学习,持续输出的状态。写博客是一种方式,但不是每天都有想写的,值得写的东西。 有时候一个技术比较复杂,写博客的时候经常会写着写着发现自己的理解有偏差,或者细节还没有完全掌握,要去查资料,了解了之后又继续写,如此反复。 这样会导致一篇博客的耗时过长。

我在每天浏览思否、掘金和Github的过程中,发现一些比较好的想法,有JS 每日一题,NodeJS 每日一库,每天一道面试题等等等等。 https://github.com/parro-it/awesome-micro-npm-packages这个仓库收集 NodeJS 小型库,一天看一个不是梦!这也是我这个系列的灵感。 我计划每天学习一个 Go 语言的库,输出一篇介绍型的博文。每天一库当然是理想状态,我心中的预期是一周 3-5 个。

flag

简介

flagls -al-al

命令行选项在实际开发中很常用,特别是在写工具的时候。

redis-server ./redis.confredis.confpython -m SimpleHTTPServer 8080

快速使用

flag
 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
package main

import (
  "fmt"
  "flag"
)

var (
  intflag int
  boolflag bool
  stringflag string
)

func init() {
  flag.IntVar(&intflag, "intflag", 0, "int flag value")
  flag.BoolVar(&boolflag, "boolflag", false, "bool flag value")
  flag.StringVar(&stringflag, "stringflag", "default", "string flag value")
}

func main() {
  flag.Parse()

  fmt.Println("int flag:", intflag)
  fmt.Println("bool flag:", boolflag)
  fmt.Println("string flag:", stringflag)
}

可以先编译程序,然后运行(我使用的是 Win10 + Git Bash):

1
2
$ go build -o main.exe main.go
$ ./main.exe -intflag 12 -boolflag 1 -stringflag test

输出:

1
2
3
int flag: 12
bool flag: true
string flag: test

如果不设置某个选项,相应变量会取默认值:

1
$ ./main.exe -intflag 12 -boolflag 1

输出:

1
2
3
int flag: 12
bool flag: true
string flag: default
stringflagdefault
go run
1
$ go run main.go -intflag 12 -boolflag 1
-h
1
2
3
4
5
6
7
8
$ ./main.exe -h
Usage of D:\code\golang\src\github.com\darjun\cmd\flag\main.exe:
  -boolflag
        bool flag value
  -intflag int
        int flag value
  -stringflag string
        string flag value (default "default")
flag
intflag/boolflag/stringflaginitflag.TypeVarTypeInt/Uint/Float64/Booltime.Durationmainflag.Parseos.Args[1:]os.Args[0]

注意点:

flag.Parseflag.Parseinitinitmainflag.Parse

选项格式

flag
1
2
3
-flag
-flag=x
-flag x
------flag
true
1
cmd -x *
*-xfalse-xtruefalse-flag=false
-----
1
$ ./main.exe noflag -intflag 12

将会输出:

1
2
3
int flag: 0
bool flag: false
string flag: default
noflag-intflag

运行下面的程序:

1
$ ./main.exe -intflag 12 -- -boolflag=true

将会输出:

1
2
3
int flag: 12
bool flag: false
string flag: default
intflag----boolflag=trueboolflagfalse
flagflag.Argsflag.NArgflag.Arg(i)iflag.NFlag

稍稍修改一下上面的程序:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
func main() {
  flag.Parse()
    
  fmt.Println(flag.Args())
  fmt.Println("Non-Flag Argument Count:", flag.NArg())
  for i := 0; i < flag.NArg(); i++ {
    fmt.Printf("Argument %d: %s\n", i, flag.Arg(i))
  }
  
  fmt.Println("Flag Count:", flag.NFlag())
}

编译运行该程序:

1
2
$ go build -o main.exe main.go
$ ./main.exe -intflag 12 -- -stringflag test

输出:

1
2
3
4
[-stringflag test]
Non-Flag Argument Count: 2
Argument 0: -stringflag
Argument 1: test
---stringflag testflagArgs/NArg/Arg
flagstrconv.ParseIntintParseInt

布尔类型的选项值可以为:

truefalse

另一种定义选项的方式

flag.TypeVarflag.TypeTypeInt/Uint/Bool/Float64/String/Duration
 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
package main

import (
  "fmt"
  "flag"
)

var (
  intflag *int
  boolflag *bool
  stringflag *string
)

func init() {
  intflag = flag.Int("intflag", 0, "int flag value")
  boolflag = flag.Bool("boolflag", false, "bool flag value")
  stringflag = flag.String("stringflag", "default", "string flag value")
}

func main() {
  flag.Parse()
    
  fmt.Println("int flag:", *intflag)
  fmt.Println("bool flag:", *boolflag)
  fmt.Println("string flag:", *stringflag)
}

编译并运行程序:

1
2
$ go build -o main.exe main.go
$ ./main.exe -intflag 12

将输出:

1
2
3
int flag: 12
bool flag: false
string flag: default

除了使用时需要解引用,其它与前一种方式基本相同。

高级用法

定义短选项

flag
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
package main

import (
  "fmt"
  "flag"
)

var logLevel string

func init() {
  const (
    defaultLogLevel = "DEBUG"
    usage = "set log level value"
  )
  
  flag.StringVar(&logLevel, "log_type", defaultLogLevel, usage)
  flag.StringVar(&logLevel, "l", defaultLogLevel, usage + "(shorthand)")
}

func main() {
  flag.Parse()

  fmt.Println("log level:", logLevel)
}

编译、运行程序:

1
2
3
$ go build -o main.exe main.go
$ ./main.exe -log_type WARNING
$ ./main.exe -l WARNING

使用长、短选项均输出:

1
log level: WARNING

不传入该选项,输出默认值:

1
2
$ ./main.exe
log level: DEBUG

解析时间间隔

flagtime.Durationflagtime.ParseDuration
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
package main

import (
  "flag"
  "fmt"
  "time"
)

var (
  period time.Duration
)

func init() {
  flag.DurationVar(&period, "period", 1*time.Second, "sleep period")
}

func main() {
  flag.Parse()
  fmt.Printf("Sleeping for %v...", period)
  time.Sleep(period)
  fmt.Println()
}
period
1
2
3
4
5
6
$ go build -o main.exe main.go
$ ./main.exe
Sleeping for 1s...

$ ./main.exe -period 1m30s
Sleeping for 1m30s...

自定义选项

flag
 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
package main

import (
  "errors"
  "flag"
  "fmt"
  "strings"
  "time"
)

type interval []time.Duration

func (i *interval) String() string {
  return fmt.Sprint(*i)
}

func (i *interval) Set(value string) error {
  if len(*i) > 0 {
    return errors.New("interval flag already set")
  }
  for _, dt := range strings.Split(value, ",") {
    duration, err := time.ParseDuration(dt)
    if err != nil {
      return err
    }
    *i = append(*i, duration)
  }
  return nil
}

var (
  intervalFlag interval
)

func init() {
  flag.Var(&intervalFlag, "deltaT", "comma-seperated list of intervals to use between events")
}

func main() {
  flag.Parse()

  fmt.Println(intervalFlag)
}
interval
flag.Value
1
2
3
4
5
// src/flag/flag.go
type Value interface {
  String() string
  Set(string) error
}
Stringflag.ParseSet,
flag.Var

编译、执行程序:

1
2
3
4
5
$ go build -o main.exe main.go
$ ./main.exe -deltaT 30s
[30s]
$ ./main.exe -deltaT 30s,1m,1m30s
[30s 1m0s 1m30s]
SeterrorParse
1
2
3
4
5
$ ./main.exe -deltaT 30x
invalid value "30x" for flag -deltaT: time: unknown unit x in duration 30x
Usage of D:\code\golang\src\github.com\darjun\go-daily-lib\flag\self-defined\main.exe:
  -deltaT value
        comma-seperated list of intervals to use between events

解析程序中的字符串

flag.FlagSet
flagFlagSetflagFlagSetCommandLineflagCommandLine
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
// src/flag/flag.go
var CommandLine = NewFlagSet(os.Args[0], ExitOnError)

func Parse() {
  CommandLine.Parse(os.Args[1:])
}

func IntVar(p *int, name string, value int, usage string) {
  CommandLine.Var(newIntValue(value, p), name, usage)
}

func Int(name string, value int, usage string) *int {
  return CommandLine.Int(name, value, usage)
}

func NFlag() int { return len(CommandLine.actual) }

func Arg(i int) string {
  return CommandLine.Arg(i)
}

func NArg() int { return len(CommandLine.args) }
FlagSet
 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
package main

import (
  "flag"
  "fmt"
)

func main() {
  args := []string{"-intflag", "12", "-stringflag", "test"}

  var intflag int
  var boolflag bool
  var stringflag string

  fs := flag.NewFlagSet("MyFlagSet", flag.ContinueOnError)
  fs.IntVar(&intflag, "intflag", 0, "int flag value")
  fs.BoolVar(&boolflag, "boolflag", false, "bool flag value")
  fs.StringVar(&stringflag, "stringflag", "default", "string flag value")

  fs.Parse(args)
  
  fmt.Println("int flag:", intflag)
  fmt.Println("bool flag:", boolflag)
  fmt.Println("string flag:", stringflag)
}
NewFlagSet
ContinueOnErrorCommandLineExitOnErroros.Exit(2)PanicOnError
flag
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
// src/flag/flag.go
func (f *FlagSet) Parse(arguments []string) error {
  f.parsed = true
  f.args = arguments
  for {
    seen, err := f.parseOne()
    if seen {
      continue
    }
    if err == nil {
      break
    }
    switch f.errorHandling {
    case ContinueOnError:
      return err
    case ExitOnError:
      os.Exit(2)
    case PanicOnError:
      panic(err)
    }
  }
  return nil
}
flagFlagSetParseflag.ParseCommandLine.Parse(os.Args[1:])