zhuts 106323a6a3 feat: 新增第十九课命令行参数与交互输入例程 1 день назад
..
README.md 106323a6a3 feat: 新增第十九课命令行参数与交互输入例程 1 день назад
main.go 106323a6a3 feat: 新增第十九课命令行参数与交互输入例程 1 день назад
main_test.go 106323a6a3 feat: 新增第十九课命令行参数与交互输入例程 1 день назад

README.md

第 19 课:命令行输入与参数

学习目标

让程序从命令行参数或键盘取得姓名、学习分钟数,理解 os.Args、flag、标准输入输出与基本校验。先看 main.go,运行两种模式,再看 main_test.go。

本课只使用标准库,共享根目录的 go.mod,无需安装依赖;不会写入或修改学习记录文件。当前程序每次运行只处理一条记录,后续综合项目再结合 JSON 存储。

1. 编译程序

在仓库根目录的 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. 参数模式

.\bin\19_cli\study.exe -name 小朱 -minutes 30

输出:

学习者:小朱,学习时长:30 分钟

姓名包含空格时加引号:

.\bin\19_cli\study.exe -name "小 朱" -minutes 45

不提前编译也能使用 go run,它仍会先编译再运行:

go run ./lessons/19_cli -name 小朱 -minutes 30

参数写在包路径之后。也可以写 -name=小朱 -minutes=30。不提供姓名或分钟数、分钟数不在 1~1440 之间、整数格式错误,都会报告错误并以非零状态退出。

查看帮助:

.\bin\19_cli\study.exe -h

-h / -help 在本程序中显示帮助并正常结束。参数错误由 flag 输出提示,本程序再输出错误摘要;因此有时会看到两部分信息。

3. 交互模式

.\bin\19_cli\study.exe -interactive

按提示输入两行:

请输入姓名:小朱
请输入学习分钟数:45
学习者:小朱,学习时长:45 分钟

输入的姓名和分钟数会去掉两端空白。空姓名、非整数或超范围分钟数会报错,不会重复要求输入;结束后重新运行即可。

也可以把输入传给程序,例如 PowerShell:

"小朱", "45" | .\bin\19_cli\study.exe -interactive

管道中不会像交互终端一样回显你输入的每一行,因此提示和结果可能显示在同一行。实际终端编码依赖 Windows / PowerShell 配置,出现乱码时先检查终端编码。

两种输入来源不可混用:-interactive 不能同时显式指定 -name 或 -minutes,即使它们的值是空字符串或 0 也不行。帮助中布尔选项单独写 -interactive 等价于 -interactive=true;-interactive=false 表示参数模式。

4. os.Args 与 flag

os.Args[0]  // 通常是程序名或可执行文件路径。
os.Args[1:] // 用户输入的参数,不含程序名。

参数原始上是字符串切片。标准库 flag 根据声明解析带名称的参数,避免自己逐个处理 -name 之类的文本。

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 表示失败。

.\bin\19_cli\study.exe -name 小朱 -minutes 0
$LASTEXITCODE

os.Exit 不执行当前进程已登记的 defer。本例 run 已经返回,且没有需要释放的外部资源;以后涉及文件或网络时,应先关闭资源,再决定是否退出。

测试与练习

在仓库根目录执行:

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 · 学习总览