Skip to content
Go
flag — 命令行参数解析

flag — 命令行参数解析

flag 是 Go 内置的命令行参数解析库,标准库命令与多数服务端进程都用它。语法与行为以下均按 go1.27.0 实测核对。


1. 标志语法

包文档(src/flag/flag.go)列出的合法形式:

-flag
--flag      // 单双横线等价
-flag=x
-flag x     // 仅限非布尔标志(non-boolean flags only)

布尔标志不能用空格带值,关掉它必须写 -flag=false。解析在第一个非标志参数处停止(单独一个 - 也算非标志参数),-- 是显式终止符;整数标志接受 1234、0664、0x1234,可为负。由此产生经典陷阱(实测):

fs := flag.NewFlagSet("c1", flag.ContinueOnError)
verbose := fs.Bool("verbose", false, "verbose output")
port := fs.Int("port", 8080, "server port")
fs.Parse([]string{"-verbose", "true", "-port", "9090"})
// 实测:verbose=true port=8080 Args=["true" "-port" "9090"]

-verbose 自身即为 true;true 是第一个非标志参数,解析到此停止,-port 根本没被处理。布尔标志后跟值不是赋值,是提前终止。


2. 定义与解析

返回指针:

name    := flag.String("name", "default", "your name")
age     := flag.Int("age", 0, "your age")
verbose := flag.Bool("verbose", false, "verbose output")

绑定到已有变量(避免解引用,库代码更推荐):

var name string
flag.StringVar(&name, "name", "default", "your name")

自定义类型用 flag.Var(实现 String()/Set(string) error,见第 6 节完整示例);不需要存值的"出现即回调"用 flag.BoolFunc(Go 1.21,api/go1.21.txt 核实),实测 -reload 时回调收到 "true"、-reload=now 收到 "now"。标志注册完必须调用 flag.Parse()(解析 os.Args[1:]),否则一律是默认值。

包级函数都转发到包级变量 flag.CommandLine(一个 *FlagSet,默认名 os.Args[0]、ExitOnError 模式);flag.CommandLine.Init(name, errorHandling) 可改错误提示里的名字与退出策略。

注意:标志名建议小写加连字符(server-addr);usage 文本里用反引号括词可替换帮助信息中显示的类型名(见第 5 节)。


3. 解析结果与非标志参数

flag.Parse()
flag.Args()   // []string,标志之后的剩余参数
flag.Arg(0)   // 按下标取
flag.NArg()   // 剩余参数个数
flag.NFlag()  // 已设置的标志个数

实测(完整程序见第 6 节):-verbose=false -name=alice -list a,b -reload -reload=now file1.txt file2.txt 解析后 Args=["file1.txt" "file2.txt"] NArg=2 NFlag=4。


4. 子命令

flag 不直接支持子命令,用 flag.NewFlagSet 按 os.Args[1] 手动分派(switch 到各 xxxCmd(os.Args[2:])):

func serveCmd(args []string) {
    fs := flag.NewFlagSet("serve", flag.ExitOnError)
    port := fs.Int("port", 8080, "server port")
    fs.Parse(args)
    fmt.Println("serving on port", *port)
}

5. 帮助信息与退出码

覆盖 flag.Usage(打印自定用法行再调 flag.PrintDefaults())。PrintDefaults 实测输出(-name/-port 显示反引号自定义类型名;布尔标志省略类型,默认值为零值时不带 “(default …)"):

  -name name
    	your name (default "guest")
  -port port
    	server port (default 8080)
  -verbose
    	verbose output

-h/-help 未定义为标志时 Parse 返回 flag.ErrHelp(实测 ContinueOnError 下 errors.Is(err, flag.ErrHelp) 为 true)。ExitOnError(包级默认)实测:-h 打印用法后进程退出码 0;未知标志打印 flag provided but not defined: -bogus 加用法后退出码 2。


6. 自定义 Var + BoolFunc 完整示例

以下程序 go1.27 实际运行,输出为真实结果:

package main

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

type stringsFlag struct{ vals []string }

func (s *stringsFlag) String() string { return strings.Join(s.vals, ",") }
func (s *stringsFlag) Set(v string) error {
	if v == "" {
		return errors.New("must not be empty")
	}
	s.vals = append(s.vals, strings.Split(v, ",")...)
	return nil
}

func main() {
	fs := flag.NewFlagSet("demo", flag.ExitOnError)
	name := fs.String("name", "guest", "user name")
	verbose := fs.Bool("verbose", false, "verbose output")
	var list stringsFlag
	fs.Var(&list, "list", "comma-separated items")
	var calls []string
	fs.BoolFunc("reload", "reload config", func(v string) error {
		calls = append(calls, fmt.Sprintf("fn(%q)", v))
		return nil
	})
	fs.Parse([]string{"-verbose=false", "-name=alice", "-list", "a,b",
		"-reload", "-reload=now", "file1.txt", "file2.txt"})
	fmt.Printf("name=%q verbose=%v list=%q calls=%v Args=%q NArg=%d NFlag=%d\n",
		*name, *verbose, list.String(), calls, fs.Args(), fs.NArg(), fs.NFlag())
}
name="alice" verbose=false list="a,b" calls=[fn("true") fn("now")] Args=["file1.txt" "file2.txt"] NArg=2 NFlag=4

Set 每个值调用一次(同一标志多次出现会累加);返回的 error 视为标志值解析错误(ContinueOnError 下从 Parse 返回)。


7. 常见陷阱

陷阱 说明
忘记 flag.Parse() 所有标志静默保持默认值
布尔标志写成 -verbose true 解析在 true 处提前停止,后续全进 Args(第 1 节实测)
并发注册/解析 Parse 前后在其他 goroutine 里注册标志是 data race;在 main 启动并发前完成注册与解析
子命令里用包级函数 包级 Parse 只认 os.Args[1:];子命令必须 fs.Parse(args)
Usage 显示临时路径名 go run 时程序名是缓存 exe 全路径;用 fs.Init/fs.SetName 固定

8. 实战建议

单工具小程序用包级 flag 就够;需要子命令、可测试解析或"出错不退出"时改用 flag.NewFlagSet + ContinueOnError,用 SetOutput 接管文案。多级子命令、必填校验、shell 补全这类需求直接上 cobra/pflag(后者语法对 POSIX 短参数更宽容),标准库 flag 的边界要心里有数。