Go by Example - Command-Line Arguments, Command-Line Flags, Command-Line Subcommands and Environment Variables

Go by Example - Command-Line Arguments, Command-Line Flags, Command-Line Subcommands and Environment Variables

介绍go中的命令行参数, 命令行标志, 命令行子命令和环境变量

Command-Line Arguments

命令行参数是参数化程序执行的常用方法。例如,go run hello.go 在 go 程序中使用了 run 和 hello.go 参数。

command-line-arguments.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
package main

import (
"fmt"
"os"
)

func main() {
// os.Args 提供了对原始命令行参数的访问
// 请注意,片段中的第一个值是程序的路径,os.Args[1:] 包含程序的参数
argsWithProg := os.Args
argsWithoutProg := os.Args[1:]

// 您可以通过普通索引获取单个参数
arg := os.Args[3]

fmt.Println(argsWithProg)
fmt.Println(argsWithoutProg)
fmt.Println(arg)
}

要尝试使用命令行参数,最好先用 go build 生成二进制文件。

log
1
2
3
4
5
$ go build command-line-arguments.go
$ ./command-line-arguments a b c d
[./command-line-arguments a b c d]
[a b c d]
c

接下来,我们将学习使用标记进行更高级的命令行处理。

Command-Line Flags

命令行标志是为命令行程序指定选项的常用方法。例如,在 wc -l 中,-l 就是一个命令行标志。

command-line-flags.go
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
package main

// Go 提供了一个支持基本命令行标志解析的 flag 包
// 我们将使用这个包来实现我们的命令行程序示例
import (
"flag"
"fmt"
)

func main() {
// 基本的标志声明可用于字符串、整数和布尔选项
// 在这里,我们声明了一个字符串标志字,其默认值为 "foo",并有一个简短的说明
// 这个 flag.String 函数返回一个字符串指针(而不是字符串值)
// 我们将在下文中看到如何使用这个指针
wordPtr := flag.String("word", "foo", "a string")

// 它声明了 numb 和 fork 标志,使用的方法与单词标志类似
numbPtr := flag.Int("numb", 42, "an int")
forkPtr := flag.Bool("fork", false, "a bool")

// 也可以使用程序中其他地方已声明的 var 来声明一个选项
// 请注意,我们需要传递一个指向标志声明函数的指针
var svar string
flag.StringVar(&svar, "svar", "bar", "a string var")

// 声明所有标志后,调用 flag.Parse() 执行命令行解析
flag.Parse()

// 在这里,我们将直接转出解析后的选项和尾部位置参数
// 请注意,我们需要使用 *wordPtr 等反引用指针来获取实际的选项值
fmt.Println("word:", *wordPtr)
fmt.Println("numb:", *numbPtr)
fmt.Println("fork:", *forkPtr)
fmt.Println("svar:", svar)
fmt.Println("tail:", flag.Args())
}

要试用命令行标志程序,最好先编译它,然后直接运行生成的二进制文件。

log
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
$ go build command-line-flags.go

# 试运行已构建的程序,首先为所有标志赋值。
$ ./command-line-flags -word=opt -numb=7 -fork -svar=flag
word: opt
numb: 7
fork: true
svar: flag
tail: []

# 请注意,如果省略标记,它们会自动使用默认值。
$ ./command-line-flags -word=opt
word: opt
numb: 42
fork: false
svar: bar
tail: []

# 可以在任何标志之后提供尾部位置参数。
$ ./command-line-flags -word=opt a1 a2 a3
word: opt
...
tail: [a1 a2 a3]

# 请注意,flag 包要求所有标志都出现在位置参数之前(否则标志将被解释为位置参数)。
$ ./command-line-flags -word=opt a1 a2 a3 -numb=7
word: opt
numb: 42
fork: false
svar: bar
tail: [a1 a2 a3 -numb=7]

# 使用 -h 或 --help标志,可为命令行程序自动生成帮助文本。
$ ./command-line-flags -h
Usage of ./command-line-flags:
-fork=false: a bool
-numb=42: an int
-svar="bar": a string var
-word="foo": a string

# 如果您提供的标志未在标志包中指定,程序将打印错误信息并再次显示帮助文本。
$ ./command-line-flags -wat
flag provided but not defined: -wat
Usage of ./command-line-flags:
...

Command-Line Subcommands

有些命令行工具,如 go 工具或 git,有许多子命令,每个子命令都有自己的标志集。例如,go build 和 go get 是 go 工具的两个不同子命令。通过 flag 包,我们可以轻松定义拥有自己标志的简单子命令。

command-line-subcommands.go
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
package main

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

func main() {
// 我们使用 NewFlagSet 函数声明一个子命令,然后为该子命令定义新的特定标记
fooCmd := flag.NewFlagSet("foo", flag.ExitOnError)
fooEnable := fooCmd.Bool("enable", false, "enable")
fooName := fooCmd.String("name", "", "name")

// 对于不同的子命令,我们可以定义不同的支持标志
barCmd := flag.NewFlagSet("bar", flag.ExitOnError)
barLevel := barCmd.Int("level", 0, "level")

// 子命令应作为程序的第一个参数
if len(os.Args) < 2 {
fmt.Println("expected 'foo' or 'bar' subcommands")
os.Exit(1)
}

// 检查调用的是哪个子命令
switch os.Args[1] {

// 对于每个子命令,我们都会解析其自身的标志,并访问尾部位置参数
case "foo":
fooCmd.Parse(os.Args[2:])
fmt.Println("subcommand 'foo'")
fmt.Println(" enable:", *fooEnable)
fmt.Println(" name:", *fooName)
fmt.Println(" tail:", fooCmd.Args())
case "bar":
barCmd.Parse(os.Args[2:])
fmt.Println("subcommand 'bar'")
fmt.Println(" level:", *barLevel)
fmt.Println(" tail:", barCmd.Args())
default:
fmt.Println("expected 'foo' or 'bar' subcommands")
os.Exit(1)
}
}
log
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
$ go build command-line-subcommands.go 

# 首先调用 foo 子命令。
$ ./command-line-subcommands foo -enable -name=joe a1 a2
subcommand 'foo'
enable: true
name: joe
tail: [a1 a2]

# 现在试试 bar。
$ ./command-line-subcommands bar -level 8 a1
subcommand 'bar'
level: 8
tail: [a1]

# 但 bar 不接受 foo 的标志。
$ ./command-line-subcommands bar -enable a1
flag provided but not defined: -enable
Usage of bar:
-level int
level

接下来,我们来看看环境变量,这是参数化程序的另一种常用方法。

Environment Variables

环境变量是向 Unix 程序传递配置信息的通用机制。让我们来看看如何设置、获取和列出环境变量。

environment-variables.go
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 (
"fmt"
"os"
"strings"
)

func main() {
// 要设置键/值对,请使用 os.Setenv
// 要获取键值,请使用 os.Getenv
// 如果环境中没有该键,则返回空字符串
os.Setenv("FOO", "1")
fmt.Println("FOO:", os.Getenv("FOO"))
fmt.Println("BAR:", os.Getenv("BAR"))

// 使用 os.Environ 列出环境中的所有键/值对
// 这将以 KEY=value 的形式返回一片字符串
// 你可以使用 strings.SplitN 来获取键和值。这里我们打印所有的键
fmt.Println()
for _, e := range os.Environ() {
pair := strings.SplitN(e, "=", 2)
fmt.Println(pair[0])
}
}

运行程序后发现,我们拾取了在程序中设置的 FOO 值,但 BAR 是空的。

log
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
$ go run environment-variables.go
FOO: 1
BAR:

# 环境中的 keys 列表将取决于您的特定机器。
TERM_PROGRAM
PATH
SHELL
...
FOO

# 如果我们先在环境中设置 BAR,运行中的程序就会选择该值。
$ BAR=2 go run environment-variables.go
FOO: 1
BAR: 2
...

参考链接

Go by Example - Command-Line Arguments, Command-Line Flags, Command-Line Subcommands and Environment Variables

https://blog.wty.cool/2024/05/25/go_by_example/Command_Line_Arguments-Command_Line_Flags-Command_Line_Subcommands-Environment_Variables/

作者

孤独小狼

发布于

2024-05-25

更新于

2024-05-25

许可协议

评论