Tarkibga o'tish

Go’da kataloglar bilan ishlash

Katalog — fayl va boshqa kataloglarni guruhlaydigan fayl tizimi obyektidir. Go dasturlarida kataloglar konfiguratsiya, log, cache, yuklangan fayllar va vaqtinchalik ma’lumotlarni tartibli saqlash uchun ishlatiladi.

Katalog bilan ishlash faqat papka yaratishdan iborat emas. Dastur operatsion tizimlar orasidagi yo‘l farqlarini, ruxsatlarni, symbolic linklarni va o‘chirish amallarining xavfini ham hisobga olishi kerak. Buning uchun asosan os va path/filepath standart paketlari ishlatiladi.

Fayl yo‘li qanday tuziladi?

Fayl yo‘li (path) fayl yoki katalogning fayl tizimidagi manzilini bildiradi. Yo‘l mutlaq (absolute) yoki nisbiy (relative) bo‘lishi mumkin:

  • mutlaq yo‘l fayl tizimi ildizidan boshlanadi;
  • nisbiy yo‘l dastur jarayonining joriy ish katalogiga nisbatan talqin qilinadi.

os.Getwd() joriy ish katalogini qaytaradi. Dastur terminaldan qaysi katalogda ishga tushirilgan bo‘lsa, nisbiy yo‘llar odatda o‘sha joydan hisoblanadi. Executable fayl turgan katalog bilan joriy ish katalogi bir xil bo‘lishi shart emas.

Yo‘l qismlarini "/" yoki "\\" bilan qo‘lda birlashtirmang. filepath.Join operatsion tizimga mos separatorni tanlaydi va ortiqcha separatorlarni tozalaydi:

package main

import (
    "fmt"
    "os"
    "path/filepath"
)

func main() {
    path := filepath.Join("data", "reports", "daily.json")
    absPath, err := filepath.Abs(path)
    if err != nil {
        fmt.Println("mutlaq yo‘lni olish:", err)
        return
    }

    fmt.Println("fayl nomi:", filepath.Base(path))
    fmt.Println("katalog:", filepath.Dir(path))
    fmt.Println("kengaytma:", filepath.Ext(path))
    fmt.Println("mutlaq yo‘l:", absPath)
    fmt.Println("mutlaqmi:", filepath.IsAbs(absPath))
    fmt.Println("separator:", string(os.PathSeparator))
}

filepath.Base oxirgi qismni, filepath.Dir ota yo‘lni, filepath.Ext esa oxirgi fayl kengaytmasini qaytaradi. Mutlaq yo‘l va separator operatsion tizim hamda dasturni qaysi katalogdan ishga tushirganingizga bog‘liq. Shu sabab bu misolning to‘liq chiqishi platformalar orasida farq qiladi.

Info

path/filepath lokal fayl tizimi yo‘llari uchun ishlatiladi. URL yo‘llari doim / bilan ajratiladi; ular uchun path yoki net/url paketi mos. Windows fayl yo‘lini URL sifatida yoki URL yo‘lini filepath.Join bilan tuzish noto‘g‘ri natija berishi mumkin.

Bitta katalog yaratish

os.Mkdir bitta katalog yaratadi. Uning ota katalogi oldindan mavjud bo‘lishi kerak.

package main

import (
    "errors"
    "fmt"
    "os"
)

func main() {
    err := os.Mkdir("katalog", 0o755)
    switch {
    case err == nil:
        fmt.Println("katalog yaratildi")
    case errors.Is(err, os.ErrExist):
        fmt.Println("katalog avvaldan mavjud")
    default:
        fmt.Println("katalog yaratilmadi:", err)
    }
}

0o755 Unix oilasidagi tizimlarda egasiga o‘qish, yozish va katalogga kirish, boshqalarga o‘qish va kirish huquqini bildiradi. Katalog uchun bajarish biti fayllarni ishga tushirishni emas, katalog ichidagi nomlarga murojaat qilishni anglatadi. Yakuniy ruxsatga umask ta’sir qilishi mumkin; Windows ruxsat modeli boshqacha.

os.Mkdirdagi os.ErrExist yo‘l mavjudligini bildiradi, ammo mavjud obyekt katalog ekanini kafolatlamaydi. Bu muhim bo‘lsa, os.Stat orqali IsDir() ham tekshiriladi.

Ichma-ich kataloglar yaratish

os.MkdirAll yo‘ldagi yetishmayotgan barcha kataloglarni yaratadi. Kataloglar allaqachon mavjud bo‘lsa, xato qaytarmaydi. Bu xususiyat funksiyani bir xil natija uchun takror chaqirishga, ya’ni idempotent ishlatishga imkon beradi.

package main

import (
    "fmt"
    "os"
    "path/filepath"
)

func main() {
    dir := filepath.Join(os.TempDir(), "go-katalog-darsi", "cache", "images")
    if err := os.MkdirAll(dir, 0o755); err != nil {
        fmt.Println("kataloglarni yaratish:", err)
        return
    }
    defer os.RemoveAll(filepath.Join(os.TempDir(), "go-katalog-darsi"))

    fmt.Println("oxirgi katalog:", filepath.Base(dir))
}

Natija:

oxirgi katalog: images

Misol operatsion tizimning vaqtinchalik katalogi ichida go-katalog-darsi/cache/images daraxtini yaratadi. defer os.RemoveAll(...) namuna yaratgan daraxtni dastur tugaganda tozalaydi. Real dasturdagi doimiy ma’lumotni bunday o‘chirish mumkin emas.

Katalog tarkibini o‘qish

os.ReadDir katalogning bevosita ichidagi yozuvlarni o‘qiydi. Ichki kataloglarga rekursiv kirmaydi. Natija nom bo‘yicha tartiblangan bo‘ladi.

package main

import (
    "fmt"
    "os"
    "path/filepath"
)

func main() {
    root, err := os.MkdirTemp("", "read-dir-*")
    if err != nil {
        fmt.Println("vaqtinchalik katalog yaratish:", err)
        return
    }
    defer os.RemoveAll(root)

    if err := os.Mkdir(filepath.Join(root, "images"), 0o755); err != nil {
        fmt.Println("ichki katalog yaratish:", err)
        return
    }
    if err := os.WriteFile(filepath.Join(root, "config.json"), []byte("{}\n"), 0o644); err != nil {
        fmt.Println("fayl yaratish:", err)
        return
    }

    entries, err := os.ReadDir(root)
    if err != nil {
        fmt.Println("katalogni o‘qish:", err)
        return
    }

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

Natija:

[FILE] config.json
[DIR] images

os.DirEntry nom va fayl turi kabi yengil ma’lumotni beradi. Hajm yoki o‘zgartirish vaqti kerak bo‘lsa, entry.Info() chaqiriladi. Bu qo‘shimcha fayl tizimi amali bo‘lishi va xato qaytarishi mumkin.

os.ReadDir barcha yozuvlarni slice ko‘rinishida xotiraga oladi. Millionlab yozuvi bor katalogda os.Open bilan katalogni ochib, file.ReadDir(n) orqali bo‘lib o‘qish xotira sarfini cheklaydi. Ochiq katalog ham *os.File bo‘lgani uchun yopilishi kerak.

Katalog daraxti bo‘ylab yurish

Ichki kataloglar va fayllarni rekursiv ko‘rish uchun filepath.WalkDir ishlatiladi. U filepath.Walkga qaraganda odatda kamroq os.Lstat chaqiradi, chunki callback’ka os.FileInfo emas, os.DirEntry beradi.

package main

import (
    "fmt"
    "io/fs"
    "os"
    "path/filepath"
)

func main() {
    root, err := os.MkdirTemp("", "walk-dir-*")
    if err != nil {
        fmt.Println("vaqtinchalik katalog yaratish:", err)
        return
    }
    defer os.RemoveAll(root)

    docs := filepath.Join(root, "docs")
    if err := os.Mkdir(docs, 0o755); err != nil {
        fmt.Println("katalog yaratish:", err)
        return
    }
    if err := os.WriteFile(filepath.Join(docs, "readme.txt"), []byte("Salom\n"), 0o644); err != nil {
        fmt.Println("fayl yaratish:", err)
        return
    }

    err = filepath.WalkDir(root, func(path string, entry fs.DirEntry, walkErr error) error {
        if walkErr != nil {
            return walkErr
        }

        rel, err := filepath.Rel(root, path)
        if err != nil {
            return err
        }
        kind := "FILE"
        if entry.IsDir() {
            kind = "DIR"
        }
        fmt.Printf("[%s] %s\n", kind, rel)
        return nil
    })
    if err != nil {
        fmt.Println("katalog bo‘ylab yurish:", err)
    }
}

Natija Unix tizimida quyidagicha ko‘rinadi:

[DIR] .
[DIR] docs
[FILE] docs/readme.txt

Windows’da oxirgi yo‘l docs\readme.txt ko‘rinishida chiqadi. Callback’dagi walkErr joriy yo‘lni o‘qishda yuz bergan xatoni bildiradi. Uni tekshirmasdan entry metodlarini chaqirish panic keltirishi mumkin.

Keraksiz ichki katalogga kirmaslik uchun katalog callback’ida filepath.SkipDir qaytariladi. Bitta faylni tashlab ketish uchun fs.SkipAll emas, callback’dan nil qaytarib keyingi yozuvga o‘tiladi. WalkDir odatda symbolic link ko‘rsatgan katalog ichiga ergashmaydi; linkning o‘zi yozuv sifatida ko‘rinadi.

Nisbiy va mutlaq yo‘llar

filepath.Abs yo‘lni joriy ish katalogiga nisbatan mutlaq ko‘rinishga keltiradi. filepath.Rel(base, target) esa basedan targetga nisbiy yo‘lni hisoblaydi. Bu funksiyalar yo‘lni mantiqiy hisoblaydi, lekin fayl mavjudligini tekshirmaydi.

filepath.Clean . va .. qismlarini tartiblaydi hamda ortiqcha separatorlarni olib tashlaydi. U xavfsizlik tekshiruvi emas. Masalan, filepath.Clean("uploads/../../config") yo‘lning ruxsat etilgan uploads katalogidan chiqib ketishini to‘xtatmaydi.

Bazaviy katalog ichidagi yo‘lni tekshirish

HTTP orqali yuklangan fayl nomi yoki CLI argumenti tashqaridan keladi. Uni bazaviy katalog bilan ko‘r-ko‘rona birlashtirish katalog bo‘ylab o‘tish (path traversal) zaifligini keltirib chiqarishi mumkin.

package main

import (
    "fmt"
    "path/filepath"
    "strings"
)

func safeJoin(base, name string) (string, error) {
    if filepath.IsAbs(name) {
        return "", fmt.Errorf("mutlaq yo‘lga ruxsat yo‘q")
    }

    baseAbs, err := filepath.Abs(base)
    if err != nil {
        return "", fmt.Errorf("bazaviy yo‘l: %w", err)
    }
    target := filepath.Join(baseAbs, name)
    rel, err := filepath.Rel(baseAbs, target)
    if err != nil {
        return "", fmt.Errorf("nisbiy yo‘l: %w", err)
    }
    if rel == ".." || strings.HasPrefix(rel, ".."+string(filepath.Separator)) {
        return "", fmt.Errorf("yo‘l bazaviy katalogdan tashqariga chiqdi")
    }
    return target, nil
}

func main() {
    for _, name := range []string{"images/logo.png", "../../config.json"} {
        path, err := safeJoin("uploads", name)
        if err != nil {
            fmt.Printf("%s: rad etildi\n", name)
            continue
        }
        fmt.Printf("%s: ruxsat berildi (%s)\n", name, filepath.Base(path))
    }
}

Natija:

images/logo.png: ruxsat berildi (logo.png)
../../config.json: rad etildi

Tekshiruv filepath.Rel natijasi .. bilan bazaviy katalogdan chiqib ketmasligini nazorat qiladi. Oddiy satr prefiksi, masalan strings.HasPrefix(target, base), yetarli emas: /data/app-old ham /data/app prefiksi bilan boshlanishi mumkin.

Warning

Bu leksik tekshiruv symbolic link orqali tashqariga chiqishni to‘liq to‘xtatmaydi. Ishonchsiz foydalanuvchi katalog ichida link yarata olsa, linklarni os.Lstat bilan tekshirish, haqiqiy yo‘lni filepath.EvalSymlinks bilan aniqlash yoki operatsion tizimning xavfsizroq, katalog deskriptoriga nisbiy API’laridan foydalanish kerak. Tekshiruv va faylni ochish orasida poyga holati ham bo‘lishi mumkin.

Katalog nomini o‘zgartirish va ko‘chirish

os.Rename(oldPath, newPath) fayl yoki katalog nomini o‘zgartiradi. Bir fayl tizimi ichida bu amal odatda tez bajariladi, chunki tarkibni nusxalash shart emas. Turli disk yoki mount nuqtalari orasida Rename xato qaytarishi mumkin. Bunday holatda tarkibni nusxalash, ruxsatlarni saqlash va eski nusxani faqat muvaffaqiyatdan keyin o‘chirish kerak.

Mavjud manzilni almashtirish qoidalari operatsion tizimga bog‘liq. Dastur Linux va Windows’da ishlashi kerak bo‘lsa, ayniqsa ochiq fayllar mavjud holatni ikkala platformada sinash kerak.

Katalogni o‘chirish

os.Remove bo‘sh katalogni o‘chiradi. Katalog ichida yozuv bo‘lsa, xato qaytaradi. os.RemoveAll esa butun daraxtni rekursiv o‘chiradi:

if err := os.RemoveAll(cacheDir); err != nil {
    return fmt.Errorf("cache katalogini o‘chirish: %w", err)
}

Bu parcha to‘liq dastur emas. U error qaytaradigan funksiya ichida yoziladi va fmt importini talab qiladi.

Warning

os.RemoveAll qaytarib bo‘lmaydigan amal. Bo‘sh satr, . yoki noto‘g‘ri hisoblangan yo‘l ko‘p ma’lumotni o‘chirishi mumkin. Yo‘l dastur boshqaradigan aniq katalog ekanini tekshirmasdan bu funksiyani chaqirmang. Foydalanuvchi yuborgan yo‘lni to‘g‘ridan-to‘g‘ri RemoveAllga bermang.

os.RemoveAll mavjud bo‘lmagan yo‘l uchun nil qaytaradi. U symbolic linkning o‘zini o‘chiradi, link ko‘rsatgan katalog daraxtiga odatda kirmaydi. Shunga qaramay, tashqi jarayonlar yo‘lni bir vaqtda o‘zgartiradigan xavfli muhitda oddiy tekshiruv yetarli bo‘lmasligi mumkin.

Vaqtinchalik kataloglar

Test, fayl konvertatsiyasi yoki bir martalik arxiv ochish uchun os.MkdirTemp xavfsiz noyob katalog nomini yaratadi:

dir, err := os.MkdirTemp("", "report-*")
if err != nil {
    return err
}
defer os.RemoveAll(dir)

Birinchi argument bo‘sh bo‘lsa, operatsion tizimning standart vaqtinchalik katalogi ishlatiladi. * tasodifiy qism bilan almashtiriladi. Funksiya katalogni yaratib bo‘lgan holda qaytaradi, shu sabab nomni oldindan tekshirib, keyin yaratishdagi poyga holati yuz bermaydi.

Uzoq ishlovchi serverda defer faqat jarayon yoki funksiya tugaganda ishlaydi. Vaqtinchalik kataloglar qancha yashashi kerakligini aniq belgilang va eski kataloglarni tozalash strategiyasini yarating.

Keng tarqalgan xatolar

Yo‘lni satr orqali birlashtirish

base + "/" + name Windows separatorini, ortiqcha separatorlarni va mutlaq ikkinchi yo‘l kabi holatlarni to‘g‘ri boshqarmaydi. Lokal yo‘llarda filepath.Join ishlating.

Mkdir va MkdirAllni bir xil deb hisoblash

Mkdir faqat oxirgi katalogni yaratadi va ota katalog mavjud bo‘lishini talab qiladi. MkdirAll yetishmayotgan ota kataloglarni ham yaratadi va mavjud daraxt uchun xato qaytarmaydi.

os.ReadDir ichki daraxtni ham o‘qiydi deb o‘ylash

os.ReadDir faqat bitta katalog darajasini qaytaradi. Rekursiv yurish uchun filepath.WalkDir kerak.

filepath.Cleanni xavfsizlik vositasi deb bilish

Clean yo‘l sintaksisini normallashtiradi, lekin ruxsat chegarasini tekshirmaydi. Bazaviy katalogdan chiqish va symbolic link holati alohida nazorat qilinadi.

Katalog ruxsatidagi bajarish bitini noto‘g‘ri tushunish

Unix tizimida katalogning x biti uning ichidagi yozuvlarga murojaat qilish imkonini beradi. Faqat r biti nomlar ro‘yxatini o‘qishga yordam beradi, lekin x bo‘lmasa ularga kirish cheklanadi.

Interviewda nimalarga e’tibor beriladi?

  • path URL kabi / separatorli yo‘llar, path/filepath esa lokal operatsion tizim yo‘llari uchun.
  • MkdirAll mavjud katalog daraxtida xato qaytarmaydi; Mkdir bitta katalog yaratadi.
  • ReadDir bir darajani, WalkDir esa daraxtni rekursiv ko‘radi.
  • WalkDir symbolic link ko‘rsatgan katalogga avtomatik ergashmaydi.
  • Yo‘lni tozalash va uning ruxsat etilgan katalog ichida ekanini tekshirish boshqa-boshqa vazifalardir.
  • RemoveAll kuchli va xavfli amal; uning argumenti ishonch chegarasida tekshiriladi.

Xulosa

Bu darsda platformaga mos yo‘l tuzish, bitta va ichma-ich kataloglar yaratish, katalog tarkibini o‘qish, daraxt bo‘ylab yurish, vaqtinchalik kataloglar va rekursiv o‘chirishni ko‘rib chiqdik. Real loyihada filepath.Joindan foydalanish, har bir xatoni tekshirish va tashqaridan kelgan yo‘lni bazaviy katalog chegarasida saqlash muhim.

Keyingi darsda Go dasturiga fayllarni kompilyatsiya vaqtida qo‘shish uchun embed paketidan foydalanishni o‘rganamiz.