Листинг директорий в Go: от простого к сложному
Работа с файловой системой — одна из базовых задач любого языка программирования. В Go она реализована настолько элегантно, что даже такая рутинная операция, как list directory in Go, превращается в увлекательное приключение. Если вы когда-нибудь задавались вопросом: "А как мне получить список файлов в папке на Go?" — вы попали по адресу. Сегодня мы разберем все возможные способы, от простого вывода до рекурсивного обхода с фильтрацией.
Почему эта тема так популярна на Stack Overflow? Потому что Go предлагает сразу несколько подходов, и выбрать правильный — половина успеха. Мы рассмотрим классический os.ReadDir, устаревший ioutil.ReadDir (но его всё ещё можно встретить в старых проектах), и мощный filepath.Walk для рекурсивного обхода. Поехали!
1. Простейший листинг: os.ReadDir
Начиная с Go 1.16, пакет os получил новый метод ReadDir, который стал стандартом де-факто. Он возвращает срез объектов os.DirEntry — легковесных описаний файлов и папок. Вот как это выглядит:
package main
import (
"fmt"
"os"
"log"
)
func main() {
// Читаем содержимое текущей директории
entries, err := os.ReadDir(".")
if err != nil {
log.Fatal(err)
}
fmt.Println("Содержимое текущей папки:")
for _, entry := range entries {
fmt.Printf("- %s (директория: %v)\n", entry.Name(), entry.IsDir())
}
}
Этот код выведет все файлы и папки в текущем каталоге. Обратите внимание: os.ReadDir не читает подпапки — только первый уровень. Если вам нужно list directory in Go рекурсивно, читайте дальше.
2. ioutil.ReadDir: старый, но знакомый
До версии Go 1.16 все использовали ioutil.ReadDir. Он возвращает []os.FileInfo — более тяжелую структуру с полной информацией о файле (размер, права доступа, время модификации). Хотя метод помечен как deprecated, вы всё ещё можете встретить его в легаси-коде:
package main
import (
"fmt"
"io/ioutil"
"log"
)
func main() {
files, err := ioutil.ReadDir("./mydir")
if err != nil {
log.Fatal(err)
}
fmt.Println("Файлы в mydir (старый способ):")
for _, file := range files {
fmt.Printf("Имя: %s, Размер: %d байт\n", file.Name(), file.Size())
}
}
Главное различие: ioutil.ReadDir сортирует результат по имени файла (как ls), а os.ReadDir — нет. Если порядок важен, используйте sort.Slice или сортировку вручную.
3. Рекурсивный обход с filepath.Walk
Когда нужно пройтись по всем подпапкам, на помощь приходит filepath.Walk. Эта функция обходит дерево каталогов, вызывая колбэк для каждого найденного элемента. Идеально для бэкапов, поиска файлов или анализа структуры проекта:
package main
import (
"fmt"
"os"
"path/filepath"
)
func main() {
root := "./myproject"
err := filepath.Walk(root, func(path string, info os.FileInfo, err error) error {
if err != nil {
return err
}
fmt.Printf("Найден: %s (размер: %d)\n", path, info.Size())
return nil
})
if err != nil {
fmt.Println("Ошибка обхода:", err)
}
}
Важный нюанс: filepath.Walk следует по симлинкам и может войти в бесконечный цикл, если есть циклические ссылки. Для продакшена используйте filepath.WalkDir (Go 1.16+) — он легче и безопаснее.
4. Фильтрация: только .go файлы или только папки
Часто нужно получить не все подряд, а только определенные типы файлов. Давайте реализуем фильтрацию с помощью strings.HasSuffix и проверки IsDir():
package main
import (
"fmt"
"os"
"strings"
)
func main() {
entries, _ := os.ReadDir(".")
fmt.Println("Только Go-файлы:")
for _, e := range entries {
if !e.IsDir() && strings.HasSuffix(e.Name(), ".go") {
fmt.Println(" ", e.Name())
}
}
fmt.Println("Только папки:")
for _, e := range entries {
if e.IsDir() {
fmt.Println(" ", e.Name())
}
}
}
Для более сложной фильтрации (по размеру, дате, правам) используйте os.Stat или получайте os.FileInfo из entry.Info().
5. Продвинутый пример: рекурсивный поиск с фильтрацией
Объединим рекурсию и фильтрацию. Допустим, нам нужно найти все файлы .log в проекте, которые весят больше 1 МБ:
package main
import (
"fmt"
"os"
"path/filepath"
"strings"
)
func main() {
root := "."
largeLogs := []string{}
err := filepath.WalkDir(root, func(path string, d os.DirEntry, err error) error {
if err != nil {
return err
}
if d.IsDir() {
return nil // пропускаем папки
}
if !strings.HasSuffix(d.Name(), ".log") {
return nil // не лог-файл
}
info, _ := d.Info()
if info.Size() > 1_000_000 { // больше 1 МБ
largeLogs = append(largeLogs, path)
}
return nil
})
if err != nil {
fmt.Println("Ошибка:", err)
return
}
fmt.Println("Большие лог-файлы ( > 1 MB):")
for _, f := range largeLogs {
fmt.Println(" -", f)
}
}
Этот код — настоящий швейцарский нож для администрирования. Его можно адаптировать под любые нужды: поиск дубликатов, очистку кэша, сбор статистики.
6. Обработка ошибок и производительность
В реальных проектах list directory in Go должен быть устойчивым к ошибкам. Вот несколько советов:
- Проверяйте права доступа: если папка защищена,
ReadDirвернёт ошибку. Оборачивайте вызов вif err != nilи логируйте. - Избегайте глобальных переменных: передавайте контекст через замыкания или структуры.
- Производительность:
os.ReadDirбыстрееioutil.ReadDir, так как не загружает полную информацию о файлах. Если нужны только имена — используйте его.