package main import ( "bufio" "errors" "flag" "fmt" "io" "os" "strconv" "strings" ) // run 接收参数、输入和输出,方便测试时不依赖真实终端。 // 参数不包含程序名,通常由 main 传入 os.Args[1:]。 func run(args []string, input io.Reader, output io.Writer) error { // ContinueOnError 让解析失败返回错误,而不是直接结束整个进程。 flags := flag.NewFlagSet("study", flag.ContinueOnError) flags.SetOutput(output) // 帮助信息和参数解析错误写到这里。 name := flags.String("name", "", "学习者姓名;交互模式可留空") minutes := flags.Int("minutes", 0, "学习时长(分钟,1~1440)") interactive := flags.Bool("interactive", false, "从标准输入读取姓名和时长") if err := flags.Parse(args); err != nil { return err } if flags.NArg() != 0 { return fmt.Errorf("不支持的位置参数:%s", strings.Join(flags.Args(), " ")) } if *interactive { // 明确禁止混用两种输入来源,避免覆盖用户提供的参数。 mixed := false flags.Visit(func(item *flag.Flag) { if item.Name == "name" || item.Name == "minutes" { mixed = true } }) if mixed { return errors.New("交互模式不能同时指定 name 或 minutes") } scanner := bufio.NewScanner(input) if _, err := fmt.Fprint(output, "请输入姓名:"); err != nil { return err } if !scanner.Scan() { if err := scanner.Err(); err != nil { return err } return errors.New("没有读取到姓名") } *name = scanner.Text() if _, err := fmt.Fprint(output, "请输入学习分钟数:"); err != nil { return err } if !scanner.Scan() { if err := scanner.Err(); err != nil { return err } return errors.New("没有读取到分钟数") } value, err := strconv.Atoi(strings.TrimSpace(scanner.Text())) if err != nil { return fmt.Errorf("分钟数必须是整数:%w", err) } *minutes = value } cleanName := strings.TrimSpace(*name) if cleanName == "" { return errors.New("姓名不能为空,请指定 -name 或使用 -interactive") } if *minutes < 1 || *minutes > 1440 { return errors.New("学习分钟数必须在 1~1440 之间") } _, err := fmt.Fprintf(output, "学习者:%s,学习时长:%d 分钟\n", cleanName, *minutes) return err } func main() { // Args[0] 通常是程序名,Args[1:] 是用户提供的参数。 if err := run(os.Args[1:], os.Stdin, os.Stdout); err != nil { if errors.Is(err, flag.ErrHelp) { return // -h 是查看帮助,不属于失败。 } fmt.Fprintln(os.Stderr, "错误:", err) os.Exit(1) } }