Avatar ·

Как получить список файлов и папок в директории на Go? (ioutil.ReadDir vs os.ReadDir)

Всем привет! Я только начал изучать Go и столкнулся с задачей: нужно прочитать содержимое папки и вывести список всех файлов и подпапок. Я погуглил и нашёл несколько способов, но не могу понять, какой из них правильный и современный. Например, я пробовал использовать `ioutil.ReadDir`, но в документации написано, что этот пакет deprecated (устарел). Ещё я видел `os.ReadDir` и `filepath.Walk`. У меня есть такой код: ```go package main import ( "fmt" "io/ioutil" "log" ) func main() { files, err := ioutil.ReadDir(".") if err != nil { log.Fatal(err) } for _, f := range files { fmt.Println(f.Name()) } } ``` Он работает, но IDE подчёркивает `ioutil` и говорит, что он устарел. Как правильно получить список файлов в Go 1.16+? Нужно ли мне сортировать результат? Как отличить файл от папки? И есть ли разница между `os.ReadDir` и `ioutil.ReadDir` кроме названия? Подскажите, пожалуйста, best practices для этой задачи. Заранее спасибо! <<>>

Правильный способ: используем os.ReadDir (Go 1.16+)

Начиная с Go 1.16, пакет ioutil объявлен устаревшим (deprecated). Вместо него рекомендуется использовать функции из пакетов os и io. Для чтения содержимого директории теперь есть две основные функции: os.ReadDir и os.ReadFile (вместо ioutil.ReadFile).

Вот минимальный, современный и корректный пример получения списка файлов и папок:

package main

import (
    "fmt"
    "log"
    "os"
)

func main() {
    entries, err := os.ReadDir(".")
    if err != nil {
        log.Fatal(err)
    }

    for _, entry := range entries {
        fmt.Println(entry.Name())
    }
}

Ключевые отличия os.ReadDir от старого ioutil.ReadDir

  • Тип возвращаемого значения: os.ReadDir возвращает срез []os.DirEntry, а не []os.FileInfo. DirEntry — это более лёгкий интерфейс, который не загружает полную информацию о файле (размер, права доступа и т.д.), пока вы это явно не запросите.
  • Производительность: os.ReadDir работает быстрее, так как не делает лишних системных вызовов stat для каждого элемента.
  • Использование памяти: DirEntry занимает меньше памяти, чем FileInfo.

Как отличить файл от папки?

Используйте метод IsDir() у DirEntry:

for _, entry := range entries {
    if entry.IsDir() {
        fmt.Printf("[DIR]  %s\n", entry.Name())
    } else {
        fmt.Printf("[FILE] %s\n", entry.Name())
    }
}

Как получить полную информацию о файле (размер, дата изменения)?

Если вам нужны метаданные (размер, время модификации, права доступа), вызовите entry.Info():

for _, entry := range entries {
    info, err := entry.Info()
    if err != nil {
        log.Printf("Ошибка получения информации о %s: %v", entry.Name(), err)
        continue
    }
    fmt.Printf("%s — размер: %d байт, изменён: %v\n", 
        entry.Name(), info.Size(), info.ModTime())
}

Сортировка результатов

os.ReadDir не гарантирует порядок элементов. Если вам нужна сортировка по имени, используйте sort.Slice:

import "sort"

entries, _ := os.ReadDir(".")
sort.Slice(entries, func(i, j int) bool {
    return entries[i].Name() < entries[j].Name()
})

Когда использовать filepath.Walk?

Функция filepath.Walk (или filepath.WalkDir в Go 1.16+) предназначена для рекурсивного обхода всех поддиректорий. Если вам нужно только содержимое одной папки (без вложенных), используйте os.ReadDir.

// Рекурсивный обход всех файлов и папок
err := filepath.WalkDir(".", func(path string, d os.DirEntry, err error) error {
    if err != nil {
        return err
    }
    fmt.Println(path)
    return nil
})

Полный пример с обработкой ошибок и фильтрацией

package main

import (
    "fmt"
    "log"
    "os"
    "sort"
    "strings"
)

func main() {
    dir := "." // можно заменить на любую папку

    entries, err := os.ReadDir(dir)
    if err != nil {
        log.Fatalf("Не удалось прочитать директорию %s: %v", dir, err)
    }

    // Сортируем по имени
    sort.Slice(entries, func(i, j int) bool {
        return entries[i].Name() < entries[j].Name()
    })

    fmt.Printf("Содержимое папки '%s':\n", dir)
    fmt.Println(strings.Repeat("-", 40))

    for _, entry := range entries {
        if entry.IsDir() {
            fmt.Printf("📁 %s/\n", entry.Name())
        } else {
            info, _ := entry.Info()
            fmt.Printf("📄 %s (%d байт)\n", entry.Name(), info.Size())
        }
    }
}

Итог: начиная с Go 1.16 всегда используйте os.ReadDir для чтения содержимого одной директории. Это быстрее, современнее и соответствует рекомендациям разработчиков Go.

<<>>

Альтернативный подход: os.Open + Readdir

Если вам нужен более тонкий контроль над процессом чтения (например, чтение порциями или работа с файловым дескриптором), можно использовать низкоуровневый способ через os.File.Readdir:

package main

import (
    "fmt"
    "log"
    "os"
)

func main() {
    f, err := os.Open(".")
    if err != nil {
        log.Fatal(err)
    }
    defer f.Close()

    // Читаем не более 10 записей за раз
    for {
        entries, err := f.Readdir(10)
        if err != nil {
            break // конец директории или ошибка
        }
        for _, entry := range entries {
            fmt.Println(entry.Name())
        }
    }
}

Этот метод возвращает []os.FileInfo (не DirEntry), поэтому он менее эффективен, но может быть полезен для очень больших директорий, когда вы хотите читать данные частями, чтобы не держать всё в памяти.

Важно: f.Readdir(n) с параметром n <= 0 читает все записи сразу, что эквивалентно ioutil.ReadDir. Передавайте положительное число для постраничного чтения.

Дополнительный совет: Если вам нужно только количество файлов или проверить, пуста ли папка, можно использовать os.ReadDir и проверить длину среза. Это проще и читаемее.