package fs

import "io/fs"

fs 包定义了文件系统的基础接口。 文件系统可由宿主操作系统提供,也可由其他包提供。

路径名称

本包中的所有接口均使用统一的路径名称语法,与宿主操作系统无关。

路径名称为 UTF-8 编码、非根目录、以斜杠分隔的路径元素序列,例如 “x/y/z”。 路径名称不能包含 “.”、“..” 或空字符串元素, 唯一特例是可使用 “.” 表示根目录。 路径不能以斜杠开头或结尾:“/x” 和 “x/” 均为无效路径。

测试

测试文件系统实现时,可使用 testing/fstest 包提供的支持。

Index

Examples

Variables

var (
	ErrInvalid    = errInvalid()    // "无效参数"
	ErrPermission = errPermission() // "权限不足"
	ErrExist      = errExist()      // "文件已存在"
	ErrNotExist   = errNotExist()   // "文件不存在"
	ErrClosed     = errClosed()     // "文件已关闭"
)

文件系统通用错误。 文件系统返回的错误可通过 errors.Is 与这些错误进行匹配判断。

var SkipAll = errors.New("skip everything and stop the walk")

SkipAll 作为 WalkDirFunc 的返回值,用于跳过所有剩余的文件和目录。 该值不会被任何函数作为错误返回。

var SkipDir = errors.New("skip this directory")

SkipDir 作为 WalkDirFunc 的返回值,用于跳过当前指定的目录。 该值不会被任何函数作为错误返回。

Functions

func FormatDirEntry

func FormatDirEntry(dir DirEntry) string

FormatDirEntry 返回 dir 的格式化文本,便于人类阅读。 DirEntry 接口的实现可在 String 方法中调用此函数。 名为 subdir 的目录和名为 hello.go 的文件,输出格式为:

d subdir/
- hello.go

func FormatFileInfo

func FormatFileInfo(info FileInfo) string

FormatFileInfo 返回 info 的格式化文本,便于人类阅读。 FileInfo 接口的实现可在 String 方法中调用此函数。 名为 "hello.go"、大小 100 字节、权限模式 0o644、 创建于 1970 年 1 月 1 日中午的文件,输出格式为:

-rw-r--r-- 100 1970-01-01 12:00:00 hello.go

func Glob

func Glob(fsys FS, pattern string) (matches []string, err error)

Glob 返回所有匹配模式 pattern 的文件名称; 若不存在匹配文件,则返回 nil。 模式的语法与 path.Match 完全一致。 模式可描述层级化路径名称,例如 usr/*/bin/ed。

Glob 会忽略文件系统错误(如读取目录时的 I/O 错误)。 唯一可能返回的错误是 path.ErrBadPattern,用于表示模式格式非法。

若 fs 实现了 GlobFS 接口,Glob 会直接调用 fs.Glob。 否则,Glob 会使用 ReadDir 遍历目录树,查找匹配模式的文件。

Example
package main

import (
	"fmt"
	"io/fs"
	"log"
	"testing/fstest"
)

func main() {
	fsys := fstest.MapFS{
		"file.txt":        {},
		"file.go":         {},
		"dir/file.txt":    {},
		"dir/file.go":     {},
		"dir/subdir/x.go": {},
	}

	patterns := []string{
		"*.txt",
		"*.go",
		"dir/*.go",
		"dir/*/x.go",
	}

	for _, pattern := range patterns {
		matches, err := fs.Glob(fsys, pattern)
		if err != nil {
			log.Fatal(err)
		}
		fmt.Printf("%q matches: %v\n", pattern, matches)
	}

}

Output:

"*.txt" matches: [file.txt]
"*.go" matches: [file.go]
"dir/*.go" matches: [dir/file.go]
"dir/*/x.go" matches: [dir/subdir/x.go]

func ReadFile

func ReadFile(fsys FS, name string) ([]byte, error)

ReadFile 从文件系统 fs 中读取指定名称的文件并返回其内容。 调用成功时返回 nil 错误,而非 io.EOF。 (因为 ReadFile 会读取整个文件,最终读取操作预期的 EOF 不会被视为需要上报的错误。)

若 fs 实现了 ReadFileFS 接口,ReadFile 会调用 fs.ReadFile。 否则,ReadFile 会调用 fs.Open,并对返回的 File 调用 Read 和 Close。

Example
package main

import (
	"fmt"
	"io/fs"
	"log"
	"testing/fstest"
)

func main() {
	fsys := fstest.MapFS{
		"hello.txt": {
			Data: []byte("Hello, World!\n"),
		},
	}

	data, err := fs.ReadFile(fsys, "hello.txt")
	if err != nil {
		log.Fatal(err)
	}

	fmt.Print(string(data))

}

Output:

Hello, World!
func ReadLink(fsys FS, name string) (string, error)

ReadLink 返回指定符号链接的目标路径。

若 fsys 未实现 ReadLinkFS 接口,ReadLink 会返回错误。

func ValidPath

func ValidPath(name string) bool

ValidPath 判断给定的路径名称是否可用于 Open 调用。

注意:所有系统(包括 Windows)的路径均使用斜杠分隔。 包含反斜杠、冒号等其他字符的路径会被视为有效路径, 但 FS 实现绝不能将这些字符解析为路径元素分隔符。 更多详情见 Path Names 章节。

Example
package main

import (
	"fmt"
	"io/fs"
)

func main() {
	paths := []string{
		".",
		"x",
		"x/y/z",
		"",
		"..",
		"/x",
		"x/",
		"x//y",
		"x/./y",
		"x/../y",
	}

	for _, path := range paths {
		fmt.Printf("ValidPath(%q) = %t\n", path, fs.ValidPath(path))
	}

}

Output:

ValidPath(".") = true
ValidPath("x") = true
ValidPath("x/y/z") = true
ValidPath("") = false
ValidPath("..") = false
ValidPath("/x") = false
ValidPath("x/") = false
ValidPath("x//y") = false
ValidPath("x/./y") = false
ValidPath("x/../y") = false

func WalkDir

func WalkDir(fsys FS, root string, fn WalkDirFunc) error

WalkDir 遍历以 root 为根的文件树,对树中的每个文件/目录(包含根节点)调用 fn 函数。

遍历文件/目录时产生的所有错误都会由 fn 函数处理: 详情见 fs.WalkDirFunc 文档。

文件按字典序遍历,保证输出结果确定性, 但这要求 WalkDir 在遍历目录前,将整个目录读取到内存中。

WalkDir 不会解析目录中的符号链接, 但若根节点 root 本身是符号链接,则会遍历其指向的目标。

Example
package main

import (
	"fmt"
	"io/fs"
	"log"
	"os"
)

func main() {
	root := "/usr/local/go/bin"
	fileSystem := os.DirFS(root)

	fs.WalkDir(fileSystem, ".", func(path string, d fs.DirEntry, err error) error {
		if err != nil {
			log.Fatal(err)
		}
		fmt.Println(path)
		return nil
	})
}

Types

type DirEntry

type DirEntry interface {
	// Name 返回条目描述的文件(或子目录)名称。
	// 该名称仅为路径的最后一个元素(基础名称),而非完整路径。
	// 例如:Name 会返回 "hello.go",而非 "home/gopher/hello.go"。
	Name() string

	// IsDir 判断该条目是否为目录。
	IsDir() bool

	// Type 返回条目的类型位。
	// 类型位是标准 FileMode 位的子集,与 FileMode.Type 方法返回值一致。
	Type() FileMode

	// Info 返回条目描述的文件或子目录的 FileInfo。
	// 返回的 FileInfo 可能来自原始目录读取时,也可能来自调用 Info 时。
	// 若目录读取后文件被删除或重命名,Info 可能返回满足 errors.Is(err, ErrNotExist) 的错误。
	// 若条目为符号链接,Info 返回链接本身的信息,而非链接目标的信息。
	Info() (FileInfo, error)
}

DirEntry 是从目录中读取的条目 (通过 ReadDir 函数或 ReadDirFile 的 ReadDir 方法获取)。

func FileInfoToDirEntry

func FileInfoToDirEntry(info FileInfo) DirEntry

FileInfoToDirEntry 返回一个从 info 中读取信息的 DirEntry。 若 info 为 nil,FileInfoToDirEntry 返回 nil。

func ReadDir

func ReadDir(fsys FS, name string) ([]DirEntry, error)

ReadDir 读取指定名称的目录, 并返回按文件名排序的目录条目列表。

若文件系统实现了 ReadDirFS 接口,ReadDir 会调用 fs.ReadDir。 否则,ReadDir 会调用 fs.Open,并对返回的文件调用 ReadDir 和 Close。

type FS

type FS interface {
	// Open 打开指定名称的文件。
	// 必须调用 [File.Close] 释放相关资源。
	//
	// 当 Open 返回错误时,该错误应为 *PathError 类型,
	// 其中 Op 字段设为 "open",Path 字段设为 name,
	// Err 字段用于描述具体问题。
	//
	// Open 应拒绝打开不满足 ValidPath(name) 的名称,
	// 并返回 Err 字段为 ErrInvalid 或 ErrNotExist 的 *PathError。
	Open(name string) (File, error)
}

FS 提供对分层文件系统的访问能力。

FS 接口是文件系统所需实现的最小接口。 文件系统可以实现额外的接口(例如 ReadFileFS), 以提供扩展或优化后的功能。

可使用 testing/fstest.TestFS 测试 FS 接口实现的正确性。

func Sub

func Sub(fsys FS, dir string) (FS, error)

Sub 返回与 fsys 中以 dir 为根目录的子树对应的 FS

若 dir 为 ".",Sub 直接返回原文件系统 fsys。 否则,若文件系统实现了 SubFS 接口,Sub 会调用 fsys.Sub(dir)。 若未实现,Sub 会返回一个新的 FS 实现 sub, 其核心逻辑是将 sub.Open(name) 转化为 fsys.Open(path.Join(dir, name))。 该实现同时会对 ReadDir、ReadFile、ReadLink、Lstat 和 Glob 方法进行适配转换。

注意:Sub(os.DirFS("/"), "prefix") 等价于 os.DirFS("/prefix"), 且这两种方式都无法保证限制操作系统访问 "/prefix" 之外的路径。 原因是 os.DirFS 的实现不会检查 "/prefix" 内部指向其他目录的符号链接。 也就是说,os.DirFS 不能作为 chroot 风格安全机制的通用替代方案, 而 Sub 函数也不会改变这一特性。

type File

type File interface {
	Stat() (FileInfo, error)
	Read([]byte) (int, error)
	Close() error
}

File 提供对单个文件的访问能力。 File 接口是文件所需实现的最小接口。 目录文件还应实现 ReadDirFile 接口。 文件可实现 io.ReaderAt 或 io.Seeker 接口作为优化方案。

type FileInfo

type FileInfo interface {
	Name() string       // 文件的基础名称
	Size() int64        // 普通文件的字节长度;其他文件为系统依赖值
	Mode() FileMode     // 文件模式位
	ModTime() time.Time // 修改时间
	IsDir() bool        // Mode().IsDir() 的简写
	Sys() any           // 底层数据源(可返回 nil)
}

FileInfo 描述文件信息,由 Stat 方法返回。

func Lstat

func Lstat(fsys FS, name string) (FileInfo, error)

Lstat 返回描述指定文件的 FileInfo。 若文件为符号链接,返回的 FileInfo 描述的是符号链接本身。 Lstat 不会尝试解析链接指向的目标文件。

若 fsys 未实现 ReadLinkFS 接口,Lstat 的行为与 Stat 完全一致。

func Stat

func Stat(fsys FS, name string) (FileInfo, error)

Stat 从文件系统中返回描述指定文件的 FileInfo

若 fs 实现了 StatFS 接口,Stat 会调用 fs.Stat。 否则,Stat 会打开 File 并获取其文件信息。

type FileMode

type FileMode uint32

FileMode 表示文件的模式和权限位。 这些位在所有系统上定义一致,因此文件信息可跨系统移植。 并非所有位都适用于所有系统。 唯一强制要求的位是目录对应的 ModeDir。

const (
	// 单个字母是 String 方法格式化时使用的缩写
	ModeDir        FileMode = 1 << (32 - 1 - iota) // d: 目录
	ModeAppend                                     // a: 仅追加模式
	ModeExclusive                                  // l: 独占使用
	ModeTemporary                                  // T: 临时文件;仅 Plan 9 系统
	ModeSymlink                                    // L: 符号链接
	ModeDevice                                     // D: 设备文件
	ModeNamedPipe                                  // p: 命名管道(FIFO)
	ModeSocket                                     // S: Unix 域套接字
	ModeSetuid                                     // u: 设置用户ID
	ModeSetgid                                     // g: 设置组ID
	ModeCharDevice                                 // c: Unix 字符设备(需同时设置 ModeDevice)
	ModeSticky                                     // t: 粘滞位
	ModeIrregular                                  // ?: 非规则文件;无其他已知属性

	// 类型位掩码。普通文件无任何类型位设置。
	ModeType = ModeDir | ModeSymlink | ModeNamedPipe | ModeSocket | ModeDevice | ModeCharDevice | ModeIrregular

	ModePerm FileMode = 0777 // Unix 权限位
)

定义的文件模式位是 FileMode 的最高有效位。 最低 9 位是标准的 Unix rwxrwxrwx 权限位。 这些位的值应视为公共 API 的一部分,可用于网络协议或磁盘存储: 绝不允许修改,仅可新增位定义。

func (FileMode) IsDir

func (m FileMode) IsDir() bool

IsDir 判断 m 是否表示目录。 即检查 m 中是否设置了 ModeDir 位。

func (FileMode) IsRegular

func (m FileMode) IsRegular() bool

IsRegular 判断 m 是否表示普通文件。 即检查未设置任何文件类型位。

func (FileMode) Perm

func (m FileMode) Perm() FileMode

Perm 返回 m 中的 Unix 权限位 (m & ModePerm)。

func (FileMode) String

func (m FileMode) String() string

func (FileMode) Type

func (m FileMode) Type() FileMode

Type 返回 m 中的类型位 (m & ModeType)。

type GlobFS

type GlobFS interface {
	FS

	// Glob 返回所有匹配模式 pattern 的文件名称,
	// 为顶层 Glob 函数提供具体实现。
	Glob(pattern string) ([]string, error)
}

GlobFS 是具备 Glob 方法的文件系统接口。

type PathError

type PathError struct {
	Op   string
	Path string
	Err  error
}

PathError 记录错误信息,以及引发错误的操作和文件路径。

func (*PathError) Error

func (e *PathError) Error() string

func (*PathError) Timeout

func (e *PathError) Timeout() bool

Timeout 判断该错误是否为超时错误。

func (*PathError) Unwrap

func (e *PathError) Unwrap() error

type ReadDirFS

type ReadDirFS interface {
	FS

	// ReadDir 读取指定名称的目录,
	// 并返回按文件名排序的目录条目列表。
	ReadDir(name string) ([]DirEntry, error)
}

ReadDirFS 是文件系统实现的接口, 该接口为 ReadDir 提供了优化的实现。

type ReadDirFile

type ReadDirFile interface {
	File

	// ReadDir 读取目录内容,返回最多 n 个按目录顺序排列的 DirEntry 切片。
	// 对同一文件的后续调用会继续返回后续的 DirEntry。
	//
	// 若 n > 0,ReadDir 最多返回 n 个 DirEntry 结构体。
	// 这种情况下,若 ReadDir 返回空切片,会同时返回非空错误说明原因。
	// 到达目录末尾时,错误为 io.EOF。
	//(ReadDir 必须直接返回 io.EOF,而非包装 io.EOF 的错误。)
	//
	// 若 n <= 0,ReadDir 一次性返回目录中所有剩余的 DirEntry。
	// 这种情况下,若 ReadDir 执行成功(读取到目录末尾),会返回切片和 nil 错误。
	// 若在到达目录末尾前遇到错误,
	// ReadDir 会返回截至该点已读取的 DirEntry 列表和非空错误。
	ReadDir(n int) ([]DirEntry, error)
}

ReadDirFile 是可通过 ReadDir 方法读取条目的目录文件。 所有目录文件都应实现此接口。 (允许任意文件实现此接口,但非目录文件调用 ReadDir 应返回错误。)

type ReadFileFS

type ReadFileFS interface {
	FS

	// ReadFile 读取指定名称的文件并返回其内容。
	// 调用成功时返回 nil 错误,而非 io.EOF。
	//(因为 ReadFile 会读取整个文件,最终读取操作预期的 EOF
	// 不会被视为需要上报的错误。)
	//
	// 调用者可以修改返回的字节切片。
	// 该方法应返回底层数据的副本。
	ReadFile(name string) ([]byte, error)
}

ReadFileFS 是文件系统实现的接口, 该接口为 ReadFile 提供了优化的实现。

type ReadLinkFS

type ReadLinkFS interface {
	FS

	// ReadLink 返回指定符号链接的目标路径。
	// 若发生错误,错误类型应为 [*PathError]。
	ReadLink(name string) (string, error)

	// Lstat 返回描述指定文件的 [FileInfo]。
	// 若文件为符号链接,返回的 [FileInfo] 描述的是符号链接本身。
	// Lstat 不会尝试解析链接指向的目标文件。
	// 若发生错误,错误类型应为 [*PathError]。
	Lstat(name string) (FileInfo, error)
}

ReadLinkFS 是支持读取符号链接的文件系统所实现的接口。

type StatFS

type StatFS interface {
	FS

	// Stat 返回描述文件的 FileInfo。
	// 若发生错误,错误类型应为 *PathError。
	Stat(name string) (FileInfo, error)
}

StatFS 是具备 Stat 方法的文件系统接口。

type SubFS

type SubFS interface {
	FS

	// Sub 返回与以 dir 为根目录的子树对应的 FS。
	Sub(dir string) (FS, error)
}

SubFS 是具备 Sub 方法的文件系统接口。

type WalkDirFunc

type WalkDirFunc func(path string, d DirEntry, err error) error

WalkDirFunc 是 WalkDir 遍历文件/目录时调用的函数类型。

path 参数为遍历路径,以 WalkDir 的入参为前缀。 例如:以根目录 "dir" 调用 WalkDir,遍历到该目录下名为 "a" 的文件时, 遍历函数的 path 参数为 "dir/a"。

d 参数为对应路径的 DirEntry

函数返回的错误结果决定 WalkDir 的后续行为: 若返回特殊值 SkipDir,WalkDir 会跳过当前目录(d.IsDir() 为 true 时跳过 path 本身,否则跳过 path 的父目录); 若返回特殊值 SkipAll,WalkDir 会跳过所有剩余文件和目录; 若返回其他非空错误,WalkDir 会立即停止遍历并返回该错误。

err 参数用于报告路径相关的错误,代表 WalkDir 不会进入该目录遍历。 函数可自行处理该错误:如前文所述,返回错误会导致 WalkDir 停止整个目录树的遍历。

WalkDir 会在两种场景下,以非空 err 参数调用该函数:

第一,根目录的初始 Stat 操作失败时,WalkDir 会调用该函数: path 设为根目录,d 设为 nil,err 设为 fs.Stat 返回的错误。

第二,目录的 ReadDir 方法(见 ReadDirFile)执行失败时,WalkDir 会调用该函数: path 设为目录路径,d 设为描述该目录的 DirEntry,err 设为 ReadDir 返回的错误。 第二种场景下,同一个目录路径会被调用两次函数: 第一次在尝试读取目录前调用,err 为 nil,允许函数返回 SkipDirSkipAll 直接跳过读取; 第二次在 ReadDir 失败后调用,用于报告读取错误。(若 ReadDir 成功,则不会触发第二次调用。)

WalkDirFunc 与 path/filepath.WalkFunc 的区别: