
程序因 commands.Scanner 未初始化即被调用而触发 nil pointer dereference panic,核心问题在于 init() 中条件判断跳过了 source.Input(),导致全局 Scanner 始终为 nil。
程序因 `commands.scanner` 未初始化即被调用而触发 nil pointer dereference panic,核心问题在于 `init()` 中条件判断跳过了 `source.input()`,导致全局 scanner 始终为 nil。
在 Go 程序中,bufio.Scanner 是一个结构体指针类型(*bufio.Scanner),若未显式初始化就直接调用其 Scan() 方法,运行时会立即触发 panic: runtime error: invalid memory address or nil pointer dereference。你遇到的崩溃正是这一典型错误:main.go 第 13 行 commands.Scanner.Scan() 调用时,commands.Scanner 仍为 nil。
根本原因在于 init() 函数中的逻辑缺陷:
func init() {
sourceFlag := flag.String("filename", "", "if input is through source file")
flag.Parse()
if *sourceFlag != "" { // ❌ 问题所在:当未传 -filename 时,此分支不执行,source.Input() 被跳过
source.Input(*sourceFlag)
}
}
由于 flag.Parse() 在 init() 中执行,而命令行参数(如 -filename test.txt)未提供时,*sourceFlag 为空字符串,source.Input("") 不会被调用 → commands.Scanner 永远不会被赋值 → main() 中首次 commands.Scanner.Scan() 即 panic。
✅ 正确做法是无条件调用 source.Input(),将空字符串作为合法输入处理,并由 source.Input() 内部统一初始化标准流:
// main/init.go(修正后)
func init() {
sourceFlag := flag.String("filename", "", "input file path; omit to use stdin/stdout")
flag.Parse()
source.Input(*sourceFlag) // ✅ 移除 if 判断,确保始终初始化
}
同时,需同步修正 player/source/input.go,使其能安全处理空文件名:
// player/source/input.go(关键修复)
package source
import (
"bufio"
"log"
"os"
"player/command"
)
func Input(source string) {
var reader *os.File
var err error
if source != "" {
// 读取指定文件
reader, err = os.Open(source)
if err != nil {
log.Fatalf("failed to open input file %q: %v", source, err)
}
// 创建输出文件(注意:os.ModeExclusive 不适用于 os.Create;应使用 os.O_CREATE | os.O_WRONLY)
writer, err := os.Create(source + "_output.txt")
if err != nil {
log.Fatalf("failed to create output file: %v", err)
}
command.Writer = bufio.NewWriter(writer)
} else {
// 默认使用标准流
reader = os.Stdin
command.Writer = bufio.NewWriter(os.Stdout)
}
// ✅ 关键:无论何种路径,Scanner 必须在此处初始化
command.Scanner = bufio.NewScanner(reader)
}
⚠️ 注意事项:
- os.OpenFile(..., os.ModeExclusive) 是错误用法 —— os.ModeExclusive 是文件权限位(仅用于 os.MkdirAll 等),打开只读文件应直接用 os.Open();
- command.Scanner 和 command.Writer 必须是导出字段(首字母大写),且定义在 player/command 包中(如 var Scanner *bufio.Scanner),否则跨包赋值无效;
- bufio.Scanner 默认最大扫描行长度为 64KB,若需处理超长行,请在初始化后调用 Scanner.Buffer(make([]byte, 4096), 1
- 所有 bufio.Writer 需在程序退出前调用 Writer.Flush(),否则输出可能丢失(建议在 main() 结尾或 defer 中处理)。
总结:Go 的包初始化顺序要求所有依赖必须在 main() 执行前就绪。Scanner 的零值是 nil,任何对 nil 指针的解引用都会 panic。因此,确保 command.Scanner 在 main() 第一行代码执行前已被非 nil 值赋值,是避免此类崩溃的唯一可靠方式。











