使用Golang的fs.FS递归遍历常规目录和zip文件的方法
使用Golang的fs.FS递归遍历常规目录和zip文件的方法 我希望使用 fs.FS 来遍历文件系统,打印出它查看的每个目录的完整名称,并且能够继续查看 zip 文件。同样,需要打印出 zip 文件所在位置的完整目录以及 zip 文件内部的目录位置。已经尝试了很多 fs.FS 的方法,但无法弄清楚如何跳转到读取 zip 文件,然后再返回到常规文件系统。
最终希望尝试创建一个类似 grep 的程序,能够搜索所有文件,即使它们位于 zip 文件中。
有没有人拥有可以无缝遍历常规文件系统并继续进入 zip 文件的代码?
我是一名退休的数据库管理员和程序员。
1 回复
更多关于使用Golang的fs.FS递归遍历常规目录和zip文件的方法的实战系列教程也可以访问 https://www.itying.com/category-94-b0.html
以下是一个使用 fs.FS 递归遍历常规目录和 ZIP 文件的完整示例。该代码通过自定义 fs.FS 实现来无缝处理 ZIP 文件,并打印所有目录的完整路径。
package main
import (
"archive/zip"
"fmt"
"io/fs"
"os"
"path/filepath"
"strings"
)
// ZipFS 包装 zip.Reader 实现 fs.FS 接口
type ZipFS struct {
*zip.Reader
basePath string // ZIP 文件在外部文件系统中的路径
}
func (z ZipFS) Open(name string) (fs.File, error) {
return z.Reader.Open(name)
}
// 递归遍历函数
func walkFS(fsys fs.FS, basePath string, depth int) error {
return fs.WalkDir(fsys, ".", func(path string, d fs.DirEntry, err error) error {
if err != nil {
return err
}
// 构建完整路径
fullPath := filepath.Join(basePath, path)
if d.IsDir() {
// 打印目录路径
indent := strings.Repeat(" ", depth)
fmt.Printf("%s[DIR] %s\n", indent, fullPath)
// 如果是 ZIP 文件且是第一次进入(path 为文件名时)
if !d.IsDir() && strings.HasSuffix(path, ".zip") {
// 打开 ZIP 文件
file, err := fsys.Open(path)
if err != nil {
return err
}
defer file.Close()
// 获取文件信息以读取内容
fi, err := file.Stat()
if err != nil {
return err
}
// 读取 ZIP 文件
zr, err := zip.NewReader(file.(*os.File), fi.Size())
if err != nil {
return err
}
// 创建 ZIP 的 FS 并递归遍历
zipFS := ZipFS{zr, fullPath}
return walkFS(zipFS, fullPath, depth+1)
}
} else {
// 打印文件路径
indent := strings.Repeat(" ", depth)
fmt.Printf("%s[FILE] %s\n", indent, fullPath)
// 如果是 ZIP 文件,进入并遍历
if strings.HasSuffix(path, ".zip") {
// 打开 ZIP 文件
file, err := fsys.Open(path)
if err != nil {
return err
}
defer file.Close()
// 获取文件信息
fi, err := file.Stat()
if err != nil {
return err
}
// 对于 os.File 类型的处理
if osFile, ok := file.(*os.File); ok {
zr, err := zip.NewReader(osFile, fi.Size())
if err != nil {
return err
}
// 创建 ZIP 的 FS 并递归遍历
zipFS := ZipFS{zr, fullPath}
return walkFS(zipFS, fullPath+"/", depth+1)
}
}
}
return nil
})
}
// 主函数:从当前目录开始遍历
func main() {
cwd, err := os.Getwd()
if err != nil {
panic(err)
}
// 使用 os.DirFS 创建根文件系统
rootFS := os.DirFS(cwd)
fmt.Println("开始遍历文件系统(包含ZIP文件):")
fmt.Println("======================================")
if err := walkFS(rootFS, cwd, 0); err != nil {
fmt.Printf("遍历错误: %v\n", err)
}
}
对于更完整的实现,这里是一个增强版,包含更好的错误处理和 ZIP 文件检测:
package main
import (
"archive/zip"
"fmt"
"io"
"io/fs"
"os"
"path/filepath"
"strings"
)
// 增强版 ZipFS,支持完整的 fs.FS 接口
type EnhancedZipFS struct {
reader *zip.Reader
prefix string
}
func (e EnhancedZipFS) Open(name string) (fs.File, error) {
// 确保路径格式正确
name = strings.TrimPrefix(name, "./")
return e.reader.Open(name)
}
// 通用遍历函数
func traverseFileSystem(fsys fs.FS, basePath string, depth int) error {
return fs.WalkDir(fsys, ".", func(path string, d fs.DirEntry, err error) error {
if err != nil {
return err
}
fullPath := filepath.Join(basePath, path)
indent := strings.Repeat("│ ", depth)
if d.IsDir() {
fmt.Printf("%s├── 📁 %s/\n", indent, path)
} else {
fmt.Printf("%s├── 📄 %s\n", indent, path)
// 检查是否为 ZIP 文件
if strings.HasSuffix(strings.ToLower(path), ".zip") {
// 打开文件
file, err := fsys.Open(path)
if err != nil {
return err
}
// 尝试转换为 os.File 或读取全部内容
var zipReader *zip.Reader
if osFile, ok := file.(*os.File); ok {
// 如果是 os.File,可以直接使用
stat, _ := osFile.Stat()
zipReader, err = zip.NewReader(osFile, stat.Size())
file.Close()
} else {
// 如果不是 os.File,需要读取到内存
defer file.Close()
tempFile, err := os.CreateTemp("", "zipfs-*.zip")
if err != nil {
return err
}
defer os.Remove(tempFile.Name())
defer tempFile.Close()
_, err = io.Copy(tempFile, file)
if err != nil {
return err
}
tempFile.Seek(0, 0)
stat, _ := tempFile.Stat()
zipReader, err = zip.NewReader(tempFile, stat.Size())
}
if err != nil {
return fmt.Errorf("读取ZIP文件失败 %s: %v", path, err)
}
// 递归遍历 ZIP 内容
zipFS := EnhancedZipFS{reader: zipReader, prefix: fullPath}
fmt.Printf("%s│ └── 📦 进入ZIP文件:\n", indent)
return traverseFileSystem(zipFS, fullPath+"/", depth+1)
}
}
return nil
})
}
func main() {
// 使用当前目录
root := "."
absRoot, _ := filepath.Abs(root)
fmt.Printf("遍历根目录: %s\n", absRoot)
fmt.Println("══════════════════════════════════════")
fsys := os.DirFS(root)
if err := traverseFileSystem(fsys, absRoot, 0); err != nil {
fmt.Printf("错误: %v\n", err)
}
}
要创建一个类似 grep 的程序,可以修改遍历函数来搜索文件内容:
// 搜索函数示例
func searchInFS(fsys fs.FS, basePath string, searchTerm string) error {
return fs.WalkDir(fsys, ".", func(path string, d fs.DirEntry, err error) error {
if err != nil {
return err
}
if !d.IsDir() {
// 跳过 ZIP 文件(它们会被单独处理)
if strings.HasSuffix(strings.ToLower(path), ".zip") {
return processZipFile(fsys, path, basePath, searchTerm)
}
// 搜索文本文件内容
if strings.HasSuffix(strings.ToLower(path), ".txt") ||
strings.HasSuffix(strings.ToLower(path), ".go") ||
strings.HasSuffix(strings.ToLower(path), ".md") {
file, err := fsys.Open(path)
if err != nil {
return err
}
defer file.Close()
content, err := io.ReadAll(file)
if err != nil {
return err
}
if strings.Contains(string(content), searchTerm) {
fullPath := filepath.Join(basePath, path)
fmt.Printf("找到匹配: %s\n", fullPath)
}
}
}
return nil
})
}
func processZipFile(fsys fs.FS, zipPath, basePath, searchTerm string) error {
// 打开并遍历 ZIP 文件的代码
// 与上面示例类似,但调用 searchInFS 递归搜索
return nil
}
这些示例展示了如何:
- 使用
fs.FS接口统一处理常规文件和 ZIP 文件 - 递归遍历目录结构
- 正确处理 ZIP 文件内的路径
- 保持外部文件系统和 ZIP 内部文件系统的路径信息
关键点在于为 ZIP 文件实现 fs.FS 接口,然后递归调用相同的遍历函数。这样就能无缝地在常规文件系统和 ZIP 文件之间切换。

