Пакет для работы с файловой системой io/fs

“Пакет 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.

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 могли полностью обойти чтение каталога или пропустить все оставшиеся файлы и каталоги соответственно. Если чтение каталога завершилось неудачно, функция вызывается второй раз для этого каталога, чтобы сообщить об ошибке.