Go 语言文件操作详解:读取、遍历、删除目录与文件的实用方法
title = “Go 语言文件操作详解:读取、遍历、删除目录与文件的实用方法” description = “深入讲解 Go 语言中常见的文件操作方法,包括递归读取目录下所有文件、遍历文件夹树形结构、递归删除文件以及按名称精准或模糊删除文件,附完整代码示例与踩坑经验。” keywords = “Go 文件操作, Golang 读取目录, Go 递归遍历文件夹, Go 删除文件, os.ReadDir 用法, Go 文件管理” categories = [“编程开发”] tags = [“Go”, “Golang”, “文件操作”, “目录遍历”, “递归删除”, “os 包”] slug = “go-file-operations-read-traverse-delete” date = “2026-03-30” lastmod = “2026-03-30” summary = "" draft = false type = “posts” weight = 0 include_toc = false show_comments = true
Go 语言文件操作详解:读取、遍历、删除目录与文件
前言
在日常的 Go 项目开发中,操作文件和目录是非常高频的需求。无论是做日志清理、批量处理资源文件,还是构建文件管理工具,都离不开对文件的读取、遍历和删除操作。
这篇文章整理了我在实际项目中积累的几种常用文件操作方式,每一种都附带了完整的代码和使用说明。值得注意的是,Go 1.16 之后官方已经废弃了 ioutil 包,推荐使用 os.ReadDir 来替代 ioutil.ReadDir,本文的代码全部基于新的 API 编写。
递归读取目录下所有文件
开发中经常会遇到这样的场景:给定一个目录路径,需要拿到该目录下(包括所有子目录)的全部文件路径列表。比如做文件同步、资源打包的时候,这个功能几乎是必备的。
核心思路是:遍历当前目录的每一项,如果是文件就收集路径,如果是子目录就递归进去继续收集。
package main
import (
"fmt"
"os"
"path/filepath"
)
// ReadAllFiles 递归获取指定目录下的所有文件路径
func ReadAllFiles(dir string) ([]string, error) {
var files []string
entries, err := os.ReadDir(dir)
if err != nil {
return nil, fmt.Errorf("读取目录 %s 失败: %w", dir, err)
}
for _, entry := range entries {
fullPath := filepath.Join(dir, entry.Name())
if entry.IsDir() {
// 递归读取子目录
subFiles, err := ReadAllFiles(fullPath)
if err != nil {
return nil, err
}
files = append(files, subFiles...)
} else {
files = append(files, fullPath)
}
}
return files, nil
}
func main() {
files, err := ReadAllFiles("./testdir")
if err != nil {
fmt.Println("出错了:", err)
return
}
for _, f := range files {
fmt.Println(f)
}
}这里有几个细节值得说明:
- 使用
filepath.Join而不是手动拼接斜杠,这样可以自动适配 Windows 和 Linux 的路径分隔符。 - 子目录递归出错时直接返回错误,而不是静默忽略,便于排查问题。
os.ReadDir返回的是[]os.DirEntry,比旧的[]os.FileInfo性能更好,因为它不需要调用Stat获取完整文件信息。
遍历文件夹并输出树形结构
有时候我们想直观地查看一个目录的结构,类似 Linux 下的 tree 命令。用 Go 实现起来也不复杂,只需要在递归时传入一个层级参数,根据层级生成对应的缩进前缀就行。
package main
import (
"fmt"
"os"
"path/filepath"
)
// ListFileTree 以树形结构打印目录内容
// dirPath: 目录路径
// level: 当前层级深度,初始调用时传 0
func ListFileTree(dirPath string, level int) {
entries, err := os.ReadDir(dirPath)
if err != nil {
fmt.Printf("读取目录 %s 失败: %v\n", dirPath, err)
return
}
// 根据层级生成缩进前缀
prefix := ""
for i := 0; i < level; i++ {
prefix += "| "
}
prefix += "|-- "
for _, entry := range entries {
fullPath := filepath.Join(dirPath, entry.Name())
fmt.Printf("%s%s\n", prefix, entry.Name())
if entry.IsDir() {
ListFileTree(fullPath, level+1)
}
}
}
func main() {
fmt.Println("testdir/")
ListFileTree("./testdir", 0)
}运行后的输出效果大致是这样的:
testdir/
|-- config.yaml
|-- logs
| |-- app.log
| |-- error.log
|-- src
| |-- main.go
| |-- utils
| | |-- helper.go这种方式在调试或者写命令行工具的时候特别实用,能快速确认目录结构是否符合预期。
递归删除整个目录及其文件
如果需要清空一个临时目录或者缓存目录,最直接的方式是使用 os.RemoveAll。不过在调用之前,建议先用 os.Stat 检查目录是否存在,避免对一个不存在的路径执行删除操作导致不必要的错误。
package main
import (
"fmt"
"os"
)
// RemoveDir 删除指定目录及其下所有内容
func RemoveDir(dirPath string) error {
// 先检查路径是否存在
_, err := os.Stat(dirPath)
if err != nil {
if os.IsNotExist(err) {
return fmt.Errorf("目录不存在: %s", dirPath)
}
return fmt.Errorf("检查目录状态失败: %w", err)
}
// 执行递归删除
if err := os.RemoveAll(dirPath); err != nil {
return fmt.Errorf("删除目录 %s 失败: %w", dirPath, err)
}
return nil
}
func main() {
err := RemoveDir("./tmp_cache")
if err != nil {
fmt.Println(err)
return
}
fmt.Println("目录已成功删除")
}特别提醒:os.RemoveAll 的威力非常大,它会把目标路径下的所有内容一次性清除,而且不会进回收站。在生产环境中使用时,一定要做好路径校验,避免误删重要数据。
根据文件名称删除目录中的文件
在实际业务中,比起直接删掉整个目录,更常见的需求是:在某个目录中找到符合特定名称的文件并删除。这里分为两种匹配方式。
精确匹配删除
精确匹配指的是文件名(去掉扩展名后的部分)与目标名称完全一致时才删除。例如目标名称是 config,那么 config.yaml、config.json 都会被删除,但 my_config.yaml 不会。
模糊匹配删除
模糊匹配指的是文件名中包含目标字符串就删除。同样以 config 为例,config.yaml、my_config_backup.json 都会命中。
完整实现代码
下面的函数把两种模式整合在了一起,通过 fuzzy 参数来控制匹配方式:
package main
import (
"fmt"
"os"
"path/filepath"
"strings"
)
// DeleteFileByName 根据文件名称删除目录中的文件(支持递归子目录)
// dirPath: 目标目录路径
// fileName: 要匹配的文件名(不含扩展名)
// fuzzy: true 表示模糊匹配,false 表示精确匹配
func DeleteFileByName(dirPath string, fileName string, fuzzy bool) error {
if dirPath == "" || fileName == "" {
return fmt.Errorf("目录路径和文件名不能为空")
}
entries, err := os.ReadDir(dirPath)
if err != nil {
return fmt.Errorf("读取目录 %s 失败: %w", dirPath, err)
}
for _, entry := range entries {
fullPath := filepath.Join(dirPath, entry.Name())
if entry.IsDir() {
// 递归处理子目录
if err := DeleteFileByName(fullPath, fileName, fuzzy); err != nil {
return err
}
continue
}
// 判断是否命中删除条件
shouldDelete := false
if fuzzy {
// 模糊匹配:文件名中包含目标字符串
shouldDelete = strings.Contains(entry.Name(), fileName)
} else {
// 精确匹配:去掉扩展名后与目标名称一致
ext := filepath.Ext(entry.Name())
nameWithoutExt := strings.TrimSuffix(entry.Name(), ext)
shouldDelete = (nameWithoutExt == fileName)
}
if shouldDelete {
if err := os.Remove(fullPath); err != nil {
fmt.Printf("删除文件 %s 失败: %v\n", fullPath, err)
continue
}
fmt.Printf("已删除: %s\n", fullPath)
}
}
return nil
}
func main() {
// 精确匹配删除:删除所有文件名为 "temp" 的文件(如 temp.txt, temp.log)
err := DeleteFileByName("./data", "temp", false)
if err != nil {
fmt.Println("删除失败:", err)
}
// 模糊匹配删除:删除文件名中包含 "cache" 的文件
err = DeleteFileByName("./data", "cache", true)
if err != nil {
fmt.Println("删除失败:", err)
}
}这段代码的几个设计考量:
- 删除单个文件失败时不中断整个流程,而是打印错误后继续处理剩余文件,这在批量操作中更加合理。
- 使用
filepath.Ext获取扩展名,再用strings.TrimSuffix去掉扩展名,这种方式比手动查找点号更可靠。 - 支持递归进入子目录,满足大多数实际场景的需求。
踩坑记录与注意事项
在用 Go 做文件操作的过程中,有几个常见的坑值得留意:
-
ioutil包已废弃:从 Go 1.16 开始,ioutil.ReadDir被标记为 deprecated,应该改用os.ReadDir。新的 API 返回os.DirEntry而不是os.FileInfo,在只需要文件名和类型的场景下性能更好。 -
路径拼接要用
filepath.Join:不要手动用/或\拼接路径。filepath.Join会根据操作系统自动选择正确的分隔符,而且能处理多余的斜杠。 -
os.RemoveAll需要谨慎使用:这个函数不做任何确认提示就会删除所有内容。建议在调用前对路径做合法性检查,比如不允许传入根目录或者用户主目录。 -
注意文件权限问题:在 Linux 或 macOS 上,如果文件没有写权限,
os.Remove会失败。遇到权限不足的情况,需要先用os.Chmod修改权限,或者以合适的用户身份运行程序。 -
符号链接的处理:
os.ReadDir返回的DirEntry中,符号链接的IsDir()返回的是链接本身的类型,而非目标的类型。如果你的程序需要跟随符号链接,需要额外调用entry.Info()或os.Stat来获取目标信息。
常见问题
Q1:os.ReadDir 和 filepath.WalkDir 有什么区别?
os.ReadDir 只读取当前目录下的直接子项,不会自动递归。filepath.WalkDir 则会自动递归遍历整个目录树,并对每个文件或目录执行你提供的回调函数。如果你需要递归遍历,filepath.WalkDir 写起来更简洁;如果你想精细控制遍历逻辑(比如跳过某些目录),手动递归配合 os.ReadDir 会更灵活。
Q2:递归读取文件时,目录层级很深会不会栈溢出?
理论上存在这个风险,但实际中操作系统对目录深度有限制(Linux 下路径长度上限通常是 4096 字节),正常使用几乎不会触发栈溢出。如果你实在担心这个问题,可以用栈数据结构(slice 模拟栈)将递归改为迭代方式。
Q3:删除文件时提示 “permission denied” 怎么办?
这通常是文件权限不够导致的。可以先用 os.Chmod 修改文件权限,或者检查程序的运行用户是否有对应目录的写权限。在 Linux 上可以用 ls -la 查看文件的权限信息。
Q4:如何只读取特定类型的文件,比如只要 .go 文件?
在遍历时增加扩展名判断就行。用 filepath.Ext(entry.Name()) 获取文件扩展名,然后与目标扩展名比较。也可以用 filepath.Glob 做通配符匹配,但它不支持递归,只能匹配当前目录。
Q5:os.Remove 和 os.RemoveAll 的区别是什么?
os.Remove 只能删除单个文件或者空目录。如果目录不为空,会返回错误。os.RemoveAll 则会递归删除目标路径下的所有内容,包括子目录和文件,即使目录非空也能正常删除。
总结
这篇文章梳理了 Go 语言中最常用的几种文件操作场景:
- 递归读取目录文件:利用
os.ReadDir配合递归,收集目录下所有文件路径。 - 树形遍历文件夹:通过层级参数控制缩进,实现类似
tree命令的输出效果。 - 递归删除目录:使用
os.RemoveAll一键清除,但务必注意路径安全。 - 按名称删除文件:支持精确匹配和模糊匹配,覆盖不同的业务场景。
代码全部使用了 Go 1.16+ 推荐的 os.ReadDir API,如果你的项目还在用 ioutil.ReadDir,建议尽早迁移过来。
如果大家在 Go 文件操作方面还有什么疑问或者踩过什么坑,欢迎在评论区留言交流,我们一起探讨更好的实现方式~~~
版权声明
未经授权,禁止转载本文章。
如需转载请保留原文链接并注明出处。即视为默认获得授权。
未保留原文链接未注明出处或删除链接将视为侵权,必追究法律责任!
本文原文链接: https://fiveyoboy.com/articles/ go-file-operations-read-traverse-delete/
备用原文链接: https://blog.fiveyoboy.com/articles/ go-file-operations-read-traverse-delete/