如何在Go中使用Flag包(附实例)

445 阅读7分钟

Go中的flag包用于开发类似UNIX系统的程序,接受命令行参数,以某种形式操纵其行为。在Golang中,flag是一个内置包,与Go标准库一起运送。标志是一个格式化的字符串,作为参数传递给程序,这样,根据传递的标志,程序的控制或行为有一些增强的效用。本Go编程教程通过一些代码实例介绍了这一功能。

Go中的标志包是什么?

几乎所有在Windows或UNIX/Linux系统中使用的终端命令都有一些与之相关的标志。在Windows中,这些标志被称为开关,而在UNIX/Linux系统中,它们仅仅被称为标志。然而,其本质是一样的--在运行时控制程序行为。由于标志在UNIX/Linux中是一个非常常见的术语,在本教程中,我们将只关注这个平台。UNIX/Linux中使用的大多数命令已经内置了更多的实用程序。当我们在终端中给出命令时,它们通常会发挥其正常功能。如果它能达到我们的目的,那就很好。但如果我们想从程序中得到更多,我们可以查看它的许多标志,找到它们的额外能力。这些能力可以通过在命令行中执行命令时以标志的形式传递参数来加以利用。例如,如果开发人员在Linux终端输入man cat,他们可以得到以下信息。

Golang flag Package

这就打开了cat命令的手册,在这里我们可以得到关于该命令的所有信息,还有可用的标志(可能是可选的)和它们的作用。例如,程序员可以使用cat命令来打开一个文本文件,如下所示:

$ cat abc.txt

this is sample text. First line.
this is another sample text. Second line.

现在,如果我们使用一个标志,如 -n或**--编号**,这将对所有输出进行编号,如下所示:

$ cat -n abc.txt

1 this is sample text. First line.
2 this is another sample text. Second line.

尽管cat命令的基本功用是连接文件并在标准输出中打印,但它还有一些内置的功用,可以通过各种标志释放出来,如:

...
-b, --number-nonblank
              number non empty output lines, overrides -n

-n, --number
              number all output lines
...

在这种情况下,上面的标志控制着cat程序的行为。这是任何UNIX/Linux命令的典型做法。

现在,如果一个程序员想用Go程序来实现这样的行为,该怎么办?标准库中提供的标志包有助于做到这一点。请注意,有相当多的第三方包可以通过Go程序处理标志,但在本教程中,我们只关注内置库提供的标志包。

Go中flag包的使用情况?

尽管flag只不过是作为命令行参数传递的格式化字符串,但在程序中处理它们实际上并不容易或直接,特别是当我们想支持提供多个flag作为参数时。除了关注程序的逻辑之外,程序员在没有标志包的情况下,还必须写出在命令行中作为标志提供的模式的逻辑,并根据它提供适当的功能。

有时标志和文件名都可以在命令行中传递。必须有逻辑来识别并分别使用它们,因为它们实际上都是格式化的字符串,可能会有歧义。总而言之,在没有像flag这样的软件包的外部帮助下,在程序中实现标志行为需要大量的时间。因此,如果我们需要开发一个UNIX系统类型的实用程序,flag包可以使开发者的任务变得更加简单。有趣的是,Go是由具有UNIX背景的人建立的,它的许多味道会以某种形式留下痕迹,这并不奇怪。不管是什么原因,它都是Go中的一个不错的工具。

Flag Package 工具的 Go 代码示例

让我们尝试一个非常简单的例子来展示如何在Go中使用flag包:

package main

import (
	"flag"
	"fmt"
)


func main() {

	str1 := flag.String("u", "root", "username")
	str2 := flag.String("p", "", "password")
	flag.Parse()
	fmt.Println("Username : ", *str1)
	fmt.Println("Password : ", *str2)
}

请注意,这个程序可以识别两个命令行选项。-u代表用户名-p代表密码flag.String("u", "root", "username")语句定义了一个字符串命令行选项;第一个被命名为u,默认值为root。第三个参数是随程序的使用情况显示的使用字符串。因此,当运行该程序时,如下所示:

$ go run main.go

它只是以标志中给出的默认值运行。如果我们提供命令行选项:

$ go run main.go -u user123 -p secret123

提供给命令行的值在各自的变量中被接受。另外,请注意,我们可以改变顺序或只提供一个选项,如下所示--它仍然可以正常工作:

$ go run main.go -p secret123 -u user123
$ go run main.go -p secret123 
$ go run main.go -u user123

现在,当程序员提供**-h标志时会发生什么。请注意,我们没有实现任何名为h**的选项,但它仍然可以工作;它只是打印出程序的使用信息和标志:

$ go run main.go -h

这意味着很多事情实际上是由标志包在后台处理的。如果没有flag包的支持,实际上是不可能用这么少的几行代码来编写命令行功能的。一个C语言的程序员肯定能体会到Go中flag包所提供的帮助。这实际上就是Go中flag包的作用。

围棋flag包的关键功能

flag包提供了一个flag.Bool函数,它定义了一个布尔命令行选项,其名称用法字符串与flag.String函数一样。同样,还有整数无符号整数var的函数,定义了用户定义的类型值的实现。flag包会自动将与各自的函数flag相关的输入转换为其相应的值--比如将与flag.Int相关的输入转换为一个整数值。另外,它还会确保提供一个整数值,否则在运行时就会标出一个错误信息。

这个函数的变体有一个后缀为var的名字。 有一些函数叫做BoolVarStringVarIntVar等。这些函数的工作方式与它们的对应函数相同,只是没有后缀。例如,flag.Boolflag.BoolVar的区别如下:

func Bool(name string, value bool, usage string) *bool

func BoolVar(p *bool, name string, value bool, usage string)

两者都定义了一个具有指定名称默认值和使用字符串的bool标志。唯一的区别是,flag.Bool函数返回一个值,该值是存储该标志值的bool变量的地址。另一方面,flag.BoolVar接受一个额外的参数p,该参数指向一个bool变量,在该变量中存储标志的值。

还有一个名为flag.Arg和flag.Args的函数,如下所示:

func Arg(i int) string
func Args() []string

flag.Arg函数返回第1个命令行参数。**Arg(0)**是旗子处理完后剩下的第一个参数。如果请求的元素不存在,它将返回一个空字符串。flag.Args函数返回非flag命令行参数。

更多关于Go中flag包的代码示例

这里有一个小的高级Go代码例子来说明Golang中flag包的用法。这个程序的想法很简单:它将有一些排序功能,如快速排序、气泡排序等。用户将提供希望应用于所提供的数据的排序(一种或多种)算法的列表。数据是通过命令行提供的,并被转换为整数类型的值,在此基础上执行排序列表。请注意,该程序没有经过优化,只是为了说明这个概念的快速实现:

package main

import (
	"flag"
	"fmt"
	"math/rand"
	"strconv"
	"strings"
)


func BubbleSort(elements []int) []int {
	for i := 0; i < len(elements)-1; i++ {
		for j := 0; j < len(elements)-1; j++ { if elements[j] > elements[j+1] {
				elements[j+1], elements[j] = elements[j], elements[j+1]
			}
		}
	}
	return elements
}

func QuickSort(elements []int) []int {
	if len(elements) < 2 {
		return elements
	}
	l, r := 0, len(elements)-1
	pivot := rand.Int() % len(elements)
	elements[pivot], elements[r] = elements[r], elements[pivot]

	for i, _ := range elements {
		if elements[i] < elements[r] {
			elements[l], elements[i] = elements[i], elements[l]
			l++
		}
	}

	elements[l], elements[r] = elements[r], elements[l]

	QuickSort(elements[:l])
	QuickSort(elements[l+1:])

	return elements
}

func SelectionSort(elements []int) []int {
	size := len(elements)
	var mindex int
	for i := 0; i < size-1; i++ {
		mindex = i
		for j := i + 1; j < size; j++ {
			if elements[j] < elements[mindex] { mindex = j } } elements[i], elements[mindex] = elements[mindex], elements[i] } return elements } type SortTypeFlag struct { SortType []string } func (s *SortTypeFlag) GetAlgoNames() []string { return s.SortType } func (s *SortTypeFlag) String() string { return fmt.Sprint(s.SortType) } func (s *SortTypeFlag) Set(v string) error { if len(s.SortType) > 0 {
		return fmt.Errorf("cannot use names flag more than once")
	}
	names := strings.Split(v, ",")
	s.SortType = append(s.SortType, names...)
	return nil
}

func main() {

	var sorting SortTypeFlag
	flag.Var(&sorting, "sort", "Comma separated list of sorting algorithm and space separated int values, eg. -sort=quick,bubble 88 33 99 55")
	flag.Parse()

	intArr := make([]int, len(flag.Args()))
	for index, val := range flag.Args() {
		intArr[index], _ = strconv.Atoi(val)
	}

	for _, item := range sorting.GetAlgoNames() {

		switch item {
		case "quick":
			fmt.Println("Quick Sort:", QuickSort(intArr))
		case "select":
			fmt.Println("Selection Sort:", SelectionSort(intArr))
		default:
			fmt.Println("(default) Bubble Sort:", BubbleSort(intArr))
		}
	}

}


你可以按以下方式运行该程序:

$ go run main.go -sort=quick,bubble 67 34 98 10 76 12

这样做将产生以下输出:

Quick Sort: [10 12 34 67 76 98]
Selection Sort: [10 12 34 67 76 98]

关于Go中flag包的最后思考

flag包虽然在Go中不常使用,但它是标准库提供的重要设施之一,特别是当我们想创建一个类似UNIX/Linux系统的实用程序时。这个包节省了我们在命令行编程中实现标志使用逻辑的时间。事实上,如果有效地使用这个包,开发一个使用标志的程序是再简单不过了。