Golang filepath.WalkFunc使用报错:索引越界问题求助
Go代码索引越界问题排查与修复
问题背景
需求是在./task/目录下的一批名称相似、大小相同的文件中找到CSV文件,并打印其第5行第3列(索引4和2)的数值。原代码运行时触发索引越界panic,错误信息及原代码如下:
原代码
package main import ( "encoding/csv" "fmt" "os" "path/filepath" ) var s [][]string func walkfunc(path string, info os.FileInfo, err error) error { if err != nil { return err } buf, err1 := os.Open(path) if err1 == nil { var err2 error r := csv.NewReader(buf) s, err2 = r.ReadAll() if err2 == nil { fmt.Printf("found: %v", s[4][2]) } } defer buf.Close() return nil } func main() { const root = "./task/" if err := filepath.Walk(root, walkfunc); err != nil { fmt.Printf("error: %v", err) } }
错误输出
GOROOT=/usr/local/go #gosetup GOPATH=/usr/local/go/bin #gosetup /usr/local/go/bin/go build -o /private/var/folders/j2/ybr0drz13yq31dc67zmvkb1w0000gn/T/GoLand/___go_build_qwasd3_go /Users/user/Downloads/zadacha/qwasd3.go #gosetup /private/var/folders/j2/ybr0drz13yq31dc67zmvkb1w0000gn/T/GoLand/___go_build_qwasd3_go panic: runtime error: index out of range [4] with length 3 goroutine 1 [running]: main.walkfunc({0x14000018120?, 0x0?}, {0x14000098d88?, 0x10247fe40?}, {0x0?, 0x0?}) /Users/user/Downloads/zadacha/qwasd3.go:23 +0x28c path/filepath.walk({0x14000018120, 0xe}, {0x1024c9cf8, 0x140000685b0}, 0x1024c9338) /usr/local/go/src/path/filepath/path.go:433 +0xd0 path/filepath.walk({0x10248d4a8, 0x7}, {0x1024c9cf8, 0x140000684e0}, 0x1024c9338) /usr/local/go/src/path/filepath/path.go:457 +0x1fc path/filepath.Walk({0x10248d4a8, 0x7}, 0x1024c9338) /usr/local/go/src/path/filepath/path.go:520 +0x6c main.main() /Users/user/Downloads/zadacha/qwasd3.go:37 +0x30 Process finished with the exit code 2
错误原因分析
- 未过滤文件类型:
filepath.Walk会遍历目录下所有条目(包括子目录),代码没有判断当前路径是否是文件就直接打开,可能读取到目录或非CSV文件,这些文件解析后行数不足5行,导致访问s[4]时索引越界。 - 未检查CSV行列数:即使是CSV文件,也没有提前判断总行数是否≥5,以及目标行的列数是否≥3,直接访问
s[4][2]必然会在数据不满足时触发panic。 - defer位置错误:
defer buf.Close()放在if err1 == nil代码块外,当os.Open失败时buf为nil,调用Close()会引发额外panic。 - 全局变量冗余:全局变量
s会被所有遍历到的文件覆盖,且没必要使用全局变量,局部变量更安全。
修正后的代码
package main import ( "encoding/csv" "fmt" "os" "path/filepath" "strings" ) func walkfunc(path string, info os.FileInfo, err error) error { if err != nil { return err } // 跳过目录,只处理文件 if info.IsDir() { return nil } // 过滤CSV文件(可选,根据文件名后缀判断) if !strings.HasSuffix(strings.ToLower(info.Name()), ".csv") { return nil } buf, err := os.Open(path) if err != nil { fmt.Printf("open file %s failed: %v\n", path, err) return nil } defer buf.Close() // 放在open成功后,避免nil调用Close r := csv.NewReader(buf) records, err := r.ReadAll() if err != nil { fmt.Printf("parse %s as CSV failed: %v\n", path, err) return nil } // 检查行数是否足够 if len(records) < 5 { fmt.Printf("%s has only %d rows, need at least 5\n", path, len(records)) return nil } // 检查目标行的列数是否足够 targetRow := records[4] if len(targetRow) < 3 { fmt.Printf("row 5 in %s has only %d columns, need at least 3\n", path, len(targetRow)) return nil } fmt.Printf("found target value in %s: %v\n", path, targetRow[2]) // 找到目标文件后可以提前终止遍历 return filepath.SkipDir } func main() { const root = "./task/" if err := filepath.Walk(root, walkfunc); err != nil && err != filepath.SkipDir { fmt.Printf("walk directory failed: %v\n", err) } }
关键修改点
- 增加目录判断,跳过目录条目。
- 可选:通过文件名后缀过滤CSV文件(若文件名无后缀,可根据文件内容特征判断)。
- 将
defer buf.Close()移到os.Open成功后,避免nil指针调用。 - 读取CSV后检查总行数≥5、目标行列数≥3,彻底避免索引越界。
- 使用局部变量
records替代全局变量s,避免数据覆盖。 - 找到目标文件后返回
filepath.SkipDir终止遍历,提升执行效率。 - 增加错误信息打印,方便调试定位问题。
内容的提问来源于stack exchange,提问作者qwasd3lol
相关产品推荐
相关产品推荐

