# 第 19 课:命令行输入与参数 ## 学习目标 让程序从命令行参数或键盘取得姓名、学习分钟数,理解 `os.Args`、`flag`、标准输入输出与基本校验。先看 [main.go](./main.go),运行两种模式,再看 [main_test.go](./main_test.go)。 本课只使用标准库,共享根目录的 go.mod,无需安装依赖;不会写入或修改学习记录文件。当前程序每次运行只处理一条记录,后续综合项目再结合 JSON 存储。 ## 1. 编译程序 在**仓库根目录**的 PowerShell 执行: ```powershell New-Item -ItemType Directory -Force bin/19_cli | Out-Null go build -o ./bin/19_cli/study.exe ./lessons/19_cli ``` 修改源码后需要重新编译。后面的 `study.exe` 命令均假设终端仍在仓库根目录。 ## 2. 参数模式 ```powershell .\bin\19_cli\study.exe -name 小朱 -minutes 30 ``` 输出: ```text 学习者:小朱,学习时长:30 分钟 ``` 姓名包含空格时加引号: ```powershell .\bin\19_cli\study.exe -name "小 朱" -minutes 45 ``` 不提前编译也能使用 `go run`,它仍会先编译再运行: ```powershell go run ./lessons/19_cli -name 小朱 -minutes 30 ``` 参数写在包路径之后。也可以写 `-name=小朱 -minutes=30`。不提供姓名或分钟数、分钟数不在 1~1440 之间、整数格式错误,都会报告错误并以非零状态退出。 查看帮助: ```powershell .\bin\19_cli\study.exe -h ``` `-h` / `-help` 在本程序中显示帮助并正常结束。参数错误由 flag 输出提示,本程序再输出错误摘要;因此有时会看到两部分信息。 ## 3. 交互模式 ```powershell .\bin\19_cli\study.exe -interactive ``` 按提示输入两行: ```text 请输入姓名:小朱 请输入学习分钟数:45 学习者:小朱,学习时长:45 分钟 ``` 输入的姓名和分钟数会去掉两端空白。空姓名、非整数或超范围分钟数会报错,不会重复要求输入;结束后重新运行即可。 也可以把输入传给程序,例如 PowerShell: ```powershell "小朱", "45" | .\bin\19_cli\study.exe -interactive ``` 管道中不会像交互终端一样回显你输入的每一行,因此提示和结果可能显示在同一行。实际终端编码依赖 Windows / PowerShell 配置,出现乱码时先检查终端编码。 两种输入来源不可混用:`-interactive` 不能同时显式指定 `-name` 或 `-minutes`,即使它们的值是空字符串或 0 也不行。帮助中布尔选项单独写 `-interactive` 等价于 `-interactive=true`;`-interactive=false` 表示参数模式。 ## 4. os.Args 与 flag ```go os.Args[0] // 通常是程序名或可执行文件路径。 os.Args[1:] // 用户输入的参数,不含程序名。 ``` 参数原始上是字符串切片。标准库 flag 根据声明解析带名称的参数,避免自己逐个处理 `-name` 之类的文本。 ```go name := flags.String("name", "", "学习者姓名") minutes := flags.Int("minutes", 0, "学习时长") ``` String 返回 `*string`、Int 返回 `*int`,所以使用时写 `*name`、`*minutes`。解析成功后它们指向保存解析结果的变量。第九课学过的 `*` 是解引用,不是乘法。 `flag.NewFlagSet` 创建独立解析器,`flag.ContinueOnError` 让 Parse 返回错误,便于调用者和测试处理。flags.SetOutput 只控制 flag 产生的帮助/解析提示;本课 main 额外将业务错误输出到标准错误。 `flags.Visit` 遍历**显式设置过**的选项。本例借它区分“没有设置”和“明确设置为默认值”,用于禁止混合输入。它的回调是第十三课学过的函数参数,不需要在这里记住完整接口。 flag 遇到第一个非 flag 位置参数后停止解析;本例用 `NArg` 检查并拒绝所有位置参数,因此请把选项写在前面,避免将输入误当作合法参数。单独的 `--` 会结束选项解析,后续内容也是位置参数,在本例会被拒绝。 ## 5. 标准输入、输出与测试 - `os.Stdin`:标准输入,交互时通常连接键盘,也可以来自管道。 - `os.Stdout`:标准输出,正常结果写到这里。 - `os.Stderr`:标准错误,main 将最终错误写到这里。 - 传入 run 的 `io.Reader` 和 `io.Writer` 是第十六课的接口,让函数可以接收终端,也可以接收测试中准备的输入输出。 交互模式用 `bufio.Scanner` 逐行读取,Scan 后检查 Err,正常输入提前结束也会报错。Scanner 有默认单行大小限制;不适合无上限的输入。 测试中 `strings.NewReader` 模拟输入,`bytes.Buffer` 保存输出,不需要测试时真实敲键盘。这个程序的 flag 解析帮助信息写到传入的 output;若要更细致地区分标准输出和标准错误,可以进一步分别注入两个 writer。 ## 6. 退出状态 main 遇到非帮助错误时输出错误并调用 `os.Exit(1)`。PowerShell 运行 exe 后,可检查 `$LASTEXITCODE`:0 表示正常退出,非 0 表示失败。 ```powershell .\bin\19_cli\study.exe -name 小朱 -minutes 0 $LASTEXITCODE ``` `os.Exit` 不执行当前进程已登记的 defer。本例 run 已经返回,且没有需要释放的外部资源;以后涉及文件或网络时,应先关闭资源,再决定是否退出。 ## 测试与练习 在仓库根目录执行: ```powershell go test -count=1 -v ./lessons/19_cli ``` 测试覆盖参数模式、交互模式、帮助、空输入、混用模式、参数错误和时长边界,不需要人工输入。 - [ ] 用你的姓名和不同分钟数运行两种模式。 - [ ] 测试边界 1、1440、0、1441,观察退出状态。 - [ ] 用带空格的姓名,比较是否加引号。 - [ ] 输入非数字、只输入姓名就结束输入,观察错误。 - [ ] 增加一个 `-topic` 参数,并为它补测试。 - [ ] 修改范围规则,记得同步更改帮助文字和边界测试。 若另行手敲,放到本课的 practice 子目录,用 `go run ./lessons/19_cli/practice -name 小朱 -minutes 30` 运行,不要在同一个包里重复声明 main 或 run。 下一课学习 HTTP 客户端与简单服务,让程序通过网络交换数据。 [上一课:JSON](../18_json/README.md) · [学习总览](../../README.md)