Пакет для работы с файловой системой io/fs
Для поддержки тестирования реализаций файловых систем смотрите пакет testing/fstest.
Переменные
var (
ErrInvalid = errInvalid() // "недопустимый аргумент"
ErrPermission = errPermission() // "разрешение отклонено"
ErrExist = errExist() // "файл уже существует"
ErrNotExist = errNotExist() // "файл не существует"
ErrClosed = errClosed() // "файл уже закрыт"
)
Общие ошибки файловой системы. Ошибки, возвращаемые файловыми системами, можно проверить на соответствие этим ошибкам с помощью errors.Is.
var SkipAll = errors.New("пропустить все и остановить прогулку")
SkipAll используется как возвращаемое значение из WalkDirFunc, чтобы указать, что все оставшиеся файлы и каталоги должны быть пропущены. Ни одна функция не возвращает его в качестве ошибки.
var SkipDir = errors.New("пропустить этот каталог")
SkipDir используется в качестве возвращаемого значения WalkDirFunc, чтобы указать, что каталог, названный в вызове, должен быть пропущен. Ни одна функция не возвращает его в качестве ошибки.
Функции
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, созданного 1 января 1970 года в полдень, будет
-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 игнорирует ошибки файловой системы, такие как ошибки ввода-вывода при чтении каталогов. Единственная возможная возвращаемая ошибка — path.ErrBadPattern, сообщающая о неверном формате шаблона.
Если fs реализует GlobFS, Glob вызывает fs.Glob. В противном случае Glob использует ReadDir для обхода дерева каталогов и поиска совпадений с шаблоном.
func ReadFile
func ReadFile(fsys FS, name string) ([]byte, error)
ReadFile читает файл с указанным именем из файловой системы fs и возвращает его содержимое. Успешный вызов возвращает ошибку nil, а не io.EOF. (Поскольку ReadFile читает весь файл, ожидаемый EOF от последнего Read не рассматривается как ошибка, о которой следует сообщать).
Если fs реализует ReadFileFS, ReadFile вызывает fs.ReadFile. В противном случае ReadFile вызывает fs.Open и использует Read и Close на возвращенном File.
func ValidPath
func ValidPath(name string) bool
ValidPath сообщает, является ли данное имя пути действительным для использования в вызове Open.
Имена путей, передаваемые в open, представляют собой закодированные в UTF-8, не имеющие корня, разделенные косой чертой последовательности элементов пути, например «x/y/z». Имена путей не должны содержать элемент «.» или «..» или пустую строку, за исключением особого случая, когда имя «.» может использоваться для корневого каталога. Пути не должны начинаться или заканчиваться косой чертой: «/x» и «x/» являются недопустимыми.
Обратите внимание, что пути разделяются косой чертой во всех системах, даже в Windows. Пути, содержащие другие символы, такие как обратная косая черта и двоеточие, принимаются как действительные, но эти символы никогда не должны интерпретироваться реализацией FS как разделители элементов пути.
func WalkDir
func WalkDir(fsys FS, root string, fn WalkDirFunc) error
WalkDir проходит по дереву файлов, корнем которого является root, вызывая fn для каждого файла или каталога в дереве, включая root.
Все ошибки, возникающие при посещении файлов и каталогов, фильтруются fn: подробности см. в документации fs.WalkDirFunc.
Файлы просматриваются в лексическом порядке, что делает вывод детерминированным, но требует, чтобы WalkDir считывал весь каталог в память, прежде чем приступать к просмотру этого каталога.
WalkDir не следует по символьным ссылкам, найденным в каталогах, но если root сам является символьной ссылкой, то будет пройдена его цель.
Пример
package main
import (
"fmt"
"io/fs"
"log"
"os"
"path/filepath"
"time"
)
func main() {
// Создаем временную файловую систему для демонстрации
tmpDir, err := os.MkdirTemp("", "fs-example-*")
if err != nil {
log.Fatal(err)
}
defer os.RemoveAll(tmpDir)
// Создаем тестовые файлы и директории
createTestFS(tmpDir)
// Получаем файловую систему os.DirFS
fsys := os.DirFS(tmpDir)
// 1. Пример использования FormatDirEntry и FormatFileInfo
demoFormatFunctions(fsys)
// 2. Пример использования Glob
demoGlob(fsys)
// 3. Пример использования ReadFile
demoReadFile(fsys)
// 4. Пример использования ValidPath
demoValidPath()
// 5. Пример использования WalkDir
demoWalkDir(fsys)
}
func createTestFS(root string) {
// Создаем структуру:
// /tmp/fs-example-12345/
// ├── docs/
// │ ├── notes.md
// │ └── draft.txt
// ├── src/
// │ └── main.go
// └── README.txt
dirs := []string{
filepath.Join(root, "docs"),
filepath.Join(root, "src"),
}
files := map[string]string{
filepath.Join(root, "README.txt"): "Пример файла README",
filepath.Join(root, "docs", "notes.md"): "# Заметки\nПример содержимого",
filepath.Join(root, "docs", "draft.txt"): "Черновик документа",
filepath.Join(root, "src", "main.go"): "package main\n\nfunc main() {\n\tprintln(\"Hello\")\n}",
}
// Создаем директории
for _, dir := range dirs {
if err := os.MkdirAll(dir, 0755); err != nil {
log.Fatal(err)
}
}
// Создаем файлы
for path, content := range files {
if err := os.WriteFile(path, []byte(content), 0644); err != nil {
log.Fatal(err)
}
}
// Устанавливаем время модификации для демонстрации FormatFileInfo
modTime := time.Date(2023, time.January, 15, 12, 30, 0, 0, time.UTC)
for path := range files {
if err := os.Chtimes(path, modTime, modTime); err != nil {
log.Fatal(err)
}
}
}
func demoFormatFunctions(fsys fs.FS) {
fmt.Println("\n=== FormatDirEntry и FormatFileInfo ===")
entries, err := fs.ReadDir(fsys, ".")
if err != nil {
log.Fatal(err)
}
for _, entry := range entries {
// FormatDirEntry
fmt.Printf("DirEntry: %s\n", fs.FormatDirEntry(entry))
// Получаем FileInfo для FormatFileInfo
info, err := entry.Info()
if err != nil {
log.Fatal(err)
}
fmt.Printf("FileInfo: %s\n", fs.FormatFileInfo(info))
}
}
func demoGlob(fsys fs.FS) {
fmt.Println("\n=== Glob ===")
// Ищем все .txt файлы
matches, err := fs.Glob(fsys, "*.txt")
if err != nil {
log.Fatal(err)
}
fmt.Println("Файлы .txt в корне:", matches)
// Ищем все .go файлы в любых поддиректориях
matches, err = fs.Glob(fsys, "*/*.go")
if err != nil {
log.Fatal(err)
}
fmt.Println("Файлы .go в поддиректориях:", matches)
// Ищем все .md файлы рекурсивно
matches, err = fs.Glob(fsys, "**/*.md")
if err != nil {
log.Fatal(err)
}
fmt.Println("Файлы .md рекурсивно:", matches)
}
func demoReadFile(fsys fs.FS) {
fmt.Println("\n=== ReadFile ===")
// Читаем содержимое файла
content, err := fs.ReadFile(fsys, "src/main.go")
if err != nil {
log.Fatal(err)
}
fmt.Printf("Содержимое main.go:\n%s\n", content)
// Попытка чтения несуществующего файла
_, err = fs.ReadFile(fsys, "nonexistent.txt")
fmt.Println("Ошибка при чтении несуществующего файла:", err)
}
func demoValidPath() {
fmt.Println("\n=== ValidPath ===")
paths := []string{
"valid/path",
"invalid/../path",
"invalid\\path", // Обратные слеши не считаются разделителями
"",
"trailing/slash/",
}
for _, path := range paths {
fmt.Printf("%q valid: %t\n", path, fs.ValidPath(path))
}
}
func demoWalkDir(fsys fs.FS) {
fmt.Println("\n=== WalkDir ===")
fmt.Println("Содержимое файловой системы:")
err := fs.WalkDir(fsys, ".", func(path string, d fs.DirEntry, err error) error {
if err != nil {
return err
}
fmt.Printf("- %s\n", fs.FormatDirEntry(d))
return nil
})
if err != nil {
log.Fatal(err)
}
}
=== FormatDirEntry и FormatFileInfo ===
DirEntry: d docs/
FileInfo: drwxr-xr-x 4096 2023-01-15 12:30:00 docs
DirEntry: d src/
FileInfo: drwxr-xr-x 4096 2023-01-15 12:30:00 src
DirEntry: - README.txt
FileInfo: -rw-r--r-- 19 2023-01-15 12:30:00 README.txt
=== Glob ===
Файлы .txt в корне: [README.txt]
Файлы .go в поддиректориях: [src/main.go]
Файлы .md рекурсивно: [docs/notes.md]
=== ReadFile ===
Содержимое main.go:
package main
func main() {
println("Hello")
}
Ошибка при чтении несуществующего файла: open nonexistent.txt: file does not exist
=== ValidPath ===
"valid/path" valid: true
"invalid/../path" valid: false
"invalid\\path" valid: true
"" valid: false
"trailing/slash/" valid: false
=== WalkDir ===
Содержимое файловой системы:
- .
- d docs/
- - docs/draft.txt
- - docs/notes.md
- d src/
- - src/main.go
- - README.txt
Типы
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 или метода ReadDir класса ReadDirFile).
func FileInfoToDirEntry
func FileInfoToDirEntry(info FileInfo) DirEntry
FileInfoToDirEntry возвращает DirEntry, который возвращает информацию из info. Если info равно nil, FileInfoToDirEntry возвращает nil.
func ReadDir
func ReadDir(fsys FS, name string) ([]DirEntry, error)
ReadDir считывает указанный каталог и возвращает список записей каталога, отсортированных по имени файла.
Если fs реализует 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), возвращая *PathError с Err, установленным в
// ErrInvalid или ErrNotExist.
Open(name string) (File, error)
}
FS обеспечивает доступ к иерархической файловой системе.
Интерфейс FS является минимальной реализацией, необходимой для файловой системы. Файловая система может реализовывать дополнительные интерфейсы, такие как ReadFileFS, для предоставления дополнительных или оптимизированных функций.
testing/fstest.TestFS может использоваться для проверки правильности реализации FS.
Объяснение FS
Объяснение интерфейса fs.FS
fs.FS
- это базовый интерфейс в Go для работы с иерархическими файловыми системами. Он представляет минимальный контракт, который должна реализовать любая файловая система (реальная, виртуальная, в памяти и т.д.).
Ключевые особенности:
- Минималистичный дизайн - только один обязательный метод
Open()
- Абстракция - позволяет работать с разными ФС одинаковым способом
- Расширяемость - через дополнительные интерфейсы (
ReadFileFS
,GlobFS
и др.)
Основной метод:
Open(name string) (File, error)
- Открывает файл по имени
- Возвращает объект, реализующий
fs.File
- В случае ошибки возвращает
*fs.PathError
Пример реализации и использования
1. Создаем простую in-memory файловую систему
package main
import (
"io/fs"
"log"
"os"
"time"
)
// MemoryFS - простая in-memory реализация fs.FS
type MemoryFS struct {
files map[string]*MemoryFile
}
type MemoryFile struct {
name string
content []byte
mode fs.FileMode
modTime time.Time
}
func (m *MemoryFile) Stat() (fs.FileInfo, error) {
return &MemoryFileInfo{m}, nil
}
func (m *MemoryFile) Read(p []byte) (int, error) {
// Реализация чтения
}
func (m *MemoryFile) Close() error {
return nil
}
// MemoryFileInfo реализует fs.FileInfo
type MemoryFileInfo struct {
file *MemoryFile
}
func (m *MemoryFileInfo) Name() string { return m.file.name }
func (m *MemoryFileInfo) Size() int64 { return int64(len(m.file.content)) }
func (m *MemoryFileInfo) Mode() fs.FileMode { return m.file.mode }
func (m *MemoryFileInfo) ModTime() time.Time { return m.file.modTime }
func (m *MemoryFileInfo) IsDir() bool { return m.file.mode.IsDir() }
func (m *MemoryFileInfo) Sys() interface{} { return nil }
// Open реализует fs.FS
func (mfs *MemoryFS) Open(name string) (fs.File, error) {
if !fs.ValidPath(name) {
return nil, &fs.PathError{
Op: "open",
Path: name,
Err: fs.ErrInvalid,
}
}
file, exists := mfs.files[name]
if !exists {
return nil, &fs.PathError{
Op: "open",
Path: name,
Err: fs.ErrNotExist,
}
}
return file, nil
}
2. Используем нашу реализацию
func main() {
// Инициализируем нашу ФС
mfs := &MemoryFS{
files: map[string]*MemoryFile{
"hello.txt": {
name: "hello.txt",
content: []byte("Hello, MemoryFS!"),
mode: 0644,
modTime: time.Now(),
},
"dir": {
name: "dir",
mode: fs.ModeDir | 0755,
modTime: time.Now(),
},
},
}
// Пример 1: Открываем и читаем файл
file, err := mfs.Open("hello.txt")
if err != nil {
log.Fatal(err)
}
defer file.Close()
info, _ := file.Stat()
fmt.Printf("Файл: %s, размер: %d\n", info.Name(), info.Size())
// Пример 2: Пытаемся открыть несуществующий файл
_, err = mfs.Open("missing.txt")
if err != nil {
fmt.Printf("Ошибка: %v\n", err) // Выведет PathError
}
// Пример 3: Используем с другими функциями пакета fs
content, err := fs.ReadFile(mfs, "hello.txt")
if err != nil {
log.Fatal(err)
}
fmt.Printf("Содержимое: %s\n", content)
}
3. Пример вывода:
Файл: hello.txt, размер: 15
Ошибка: open missing.txt: file does not exist
Содержимое: Hello, MemoryFS!
Где используется fs.FS?
-
В стандартной библиотеке:
os.DirFS
- доступ к реальной файловой системеembed.FS
- доступ к встроенным файламhttp.FileSystem
- интеграция с веб-сервером
-
В популярных библиотеках:
- Виртуальные файловые системы
- Работа с архивами (zip, tar)
- Тестирование (testing/fstest)
-
В пользовательских реализациях:
- ФС в памяти
- ФС поверх облачного хранилища
- ФС для специализированных форматов
Преимущества такого подхода:
- Единообразие - один интерфейс для разных ФС
- Тестируемость - легко подменять реальную ФС на mock
- Гибкость - можно комбинировать разные реализации
Интерфейс fs.FS
стал стандартным способом работы с файловыми системами в Go 1.16+, заменив множество специализированных решений.
func Sub
func Sub(fsys FS, dir string) (FS, error)
Sub возвращает FS, соответствующую поддереву с корнем в fsys’s dir.
Если dir равна «.», Sub возвращает fsys без изменений. В противном случае, если fs реализует SubFS, Sub возвращает fsys.Sub(dir). В противном случае Sub возвращает новую реализацию FS sub, которая фактически реализует sub.Open(name) как fsys.Open(path.Join(dir, name)). Реализация также соответствующим образом преобразует вызовы ReadDir, ReadFile и 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 Stat
func Stat(fsys FS, name string) (FileInfo, error)
Stat возвращает FileInfo, описывающий файл с указанным именем из файловой системы.
Если fs реализует StatFS, Stat вызывает fs.Stat. В противном случае Stat открывает файл для его статистики.
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: setuid
ModeSetgid // g: setgid
ModeCharDevice // c: символьное устройство Unix, когда установлен ModeDevice
ModeSticky // t: sticky
ModeIrregular // ?: нерегулярный файл; ничего больше не известно об этом файле
// Маска для битов типа. Для обычных файлов не будет установлено ничего.
ModeType = ModeDir | ModeSymlink | ModeNamedPipe | ModeSocket | ModeDevice | ModeCharDevice | ModeIrregular
ModePerm FileMode = 0777 // биты разрешений Unix)
Определенные биты режима файла являются наиболее значимыми битами FileMode. Девять наименее значимых битов являются стандартными разрешениями Unix rwxrwxrwx. Значения этих битов следует рассматривать как часть общедоступного API и могут использоваться в протоколах передачи данных или представлениях диска: они не должны изменяться, хотя могут быть добавлены новые биты.
func (FileMode) IsDir
func (m FileMode) IsDir() bool
IsDir сообщает, описывает ли m каталог. То есть, он проверяет, установлен ли бит ModeDir в m.
func (FileMode) IsRegular
func (m FileMode) IsRegular() bool
IsRegular сообщает, описывает ли m обычный файл. То есть, он проверяет, что биты типа режима не установлены.
func (FileMode) Perm
func (m FileMode) Perm() FileMode
Perm возвращает биты разрешений Unix в m (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 возвращает имена всех файлов, соответствующих шаблону,
// предоставляя реализацию функции верхнего уровня
// 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, прочитанный до этого момента, и ошибку, отличную от nil.
ReadDir(n int) ([]DirEntry, error)
}
ReadDirFile — это файл каталога, записи которого можно прочитать с помощью метода ReadDir. Каждый файл каталога должен реализовывать этот интерфейс. (Любой файл может реализовывать этот интерфейс, но в этом случае ReadDir должен возвращать ошибку для некаталогов.)
type ReadFileFS
type ReadFileFS interface {
FS
// ReadFile читает указанный файл и возвращает его содержимое.
// Успешный вызов возвращает ошибку nil, а не io.EOF.
// (Поскольку ReadFile считывает весь файл, ожидаемый EOF
// от последнего Read не рассматривается как ошибка, о которой следует сообщать.)
//
// Вызывающему разрешается изменять возвращаемый байтовый срез.
// Этот метод должен возвращать копию базовых данных.
ReadFile(name string) ([]byte, error)
}
ReadFileFS — это интерфейс, реализованный файловой системой, который обеспечивает оптимизированную реализацию ReadFile.
type StatFS
type StatFS interface {
FS
// Stat возвращает FileInfo, описывающий файл.
// Если происходит ошибка, она должна быть типа *PathError.
Stat(name string) (FileInfo, error)
}
StatFS — это файловая система с методом Stat.
type SubFS
type SubFS interface {
FS
// Sub возвращает FS, соответствующий поддереву с корнем в dir.
Sub(dir string) (FS, error)
}
SubFS — это файловая система с методом Sub.
type WalkDirFunc
type WalkDirFunc func(path string, d DirEntry, err error) error
WalkDirFunc — это тип функции, вызываемой WalkDir для посещения каждого файла или каталога.
Аргумент path содержит аргумент WalkDir в качестве префикса. То есть, если WalkDir вызывается с корневым аргументом «dir» и находит файл с именем «a» в этом каталоге, функция walk будет вызвана с аргументом «dir/a».
Аргумент d — это DirEntry для указанного пути.
Результат error, возвращаемый функцией, контролирует продолжение работы WalkDir. Если функция возвращает специальное значение SkipDir, WalkDir пропускает текущий каталог (path, если d.IsDir() равно true, в противном случае — родительский каталог path). Если функция возвращает специальное значение SkipAll, WalkDir пропускает все оставшиеся файлы и каталоги. В противном случае, если функция возвращает ошибку, отличную от nil, WalkDir полностью останавливается и возвращает эту ошибку.
Аргумент err сообщает об ошибке, связанной с путем, сигнализируя, что WalkDir не будет проходить в этот каталог. Функция может решить, как обработать эту ошибку; как описано ранее, возврат ошибки приведет к тому, что WalkDir прекратит прохождение всего дерева.
WalkDir вызывает функцию с аргументом err, отличным от nil, в двух случаях.
Во-первых, если первоначальная Stat в корневом каталоге завершается с ошибкой, WalkDir вызывает функцию с path, установленным в root, d, установленным в nil, и err, установленным в ошибку из fs.Stat.
Во-вторых, если метод ReadDir каталога (см. ReadDirFile) завершается с ошибкой, WalkDir вызывает функцию с path, установленным в путь каталога, d, установленным в DirEntry, описывающий каталог, и err, установленным в ошибку из ReadDir. Во втором случае функция вызывается дважды с путем к каталогу: первый вызов происходит до попытки чтения каталога, и err установлен в nil, что дает функции возможность вернуть SkipDir или SkipAll и полностью избежать ReadDir. Второй вызов происходит после неудачного ReadDir и сообщает об ошибке из ReadDir. (Если ReadDir завершается успешно, второго вызова не происходит.)
Различия между WalkDirFunc и path/filepath.WalkFunc заключаются в следующем:
Второй аргумент имеет тип DirEntry вместо FileInfo. Функция вызывается перед чтением каталога, чтобы SkipDir или SkipAll могли полностью обойти чтение каталога или пропустить все оставшиеся файлы и каталоги соответственно. Если чтение каталога завершилось неудачно, функция вызывается второй раз для этого каталога, чтобы сообщить об ошибке.