目录

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.yamlconfig.json 都会被删除,但 my_config.yaml 不会。

模糊匹配删除

模糊匹配指的是文件名中包含目标字符串就删除。同样以 config 为例,config.yamlmy_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 做文件操作的过程中,有几个常见的坑值得留意:

  1. ioutil 包已废弃:从 Go 1.16 开始,ioutil.ReadDir 被标记为 deprecated,应该改用 os.ReadDir。新的 API 返回 os.DirEntry 而不是 os.FileInfo,在只需要文件名和类型的场景下性能更好。

  2. 路径拼接要用 filepath.Join:不要手动用 /\ 拼接路径。filepath.Join 会根据操作系统自动选择正确的分隔符,而且能处理多余的斜杠。

  3. os.RemoveAll 需要谨慎使用:这个函数不做任何确认提示就会删除所有内容。建议在调用前对路径做合法性检查,比如不允许传入根目录或者用户主目录。

  4. 注意文件权限问题:在 Linux 或 macOS 上,如果文件没有写权限,os.Remove 会失败。遇到权限不足的情况,需要先用 os.Chmod 修改权限,或者以合适的用户身份运行程序。

  5. 符号链接的处理os.ReadDir 返回的 DirEntry 中,符号链接的 IsDir() 返回的是链接本身的类型,而非目标的类型。如果你的程序需要跟随符号链接,需要额外调用 entry.Info()os.Stat 来获取目标信息。

常见问题

Q1:os.ReadDirfilepath.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.Removeos.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/