Filepath #

Path file adalah salah satu hal yang paling sering salah ditangani dalam kode yang perlu berjalan di berbagai sistem operasi. Di Windows, separator path adalah \, sementara di Linux dan macOS adalah /. Menggabungkan path dengan string concatenation biasa (dir + "/" + file) akan rusak di Windows. Package path/filepath menyelesaikan masalah ini dengan menyediakan fungsi-fungsi manipulasi path yang otomatis menggunakan separator yang tepat untuk sistem operasi yang sedang berjalan. Di samping portabilitas, filepath juga menyediakan WalkDir untuk traversal direktori rekursif, Glob untuk pencarian file dengan wildcard, serta fungsi-fungsi untuk memecah dan menganalisis komponen path.

Gambaran Besar Package path/filepath #

flowchart TD
    FP["package path/filepath"] --> Build["Membangun Path"]
    FP --> Split["Memecah Path"]
    FP --> Convert["Konversi Path"]
    FP --> Search["Pencarian File"]
    FP --> Walk["Traversal Direktori"]

    Build --> B1["filepath.Join\ngabungkan komponen path"]
    Build --> B2["filepath.Abs\npath relatif → absolut"]
    Build --> B3["filepath.FromSlash\n'/' → separator OS"]
    Build --> B4["filepath.ToSlash\nseparator OS → '/'"]

    Split --> S1["filepath.Split\n→ dir, file"]
    Split --> S2["filepath.Dir\n→ direktori saja"]
    Split --> S3["filepath.Base\n→ nama file saja"]
    Split --> S4["filepath.Ext\n→ ekstensi saja"]
    Split --> S5["filepath.SplitList\n→ pisahkan PATH env"]

    Convert --> C1["filepath.Abs\npath → absolut"]
    Convert --> C2["filepath.Rel\npath absolut → relatif"]
    Convert --> C3["filepath.Clean\nnormalisasi path"]
    Convert --> C4["filepath.EvalSymlinks\nresolve symlink"]

    Search --> G1["filepath.Glob\n'*.go', '**/*.txt'"]
    Search --> G2["filepath.Match\ncocokkan pola"]

    Walk --> W1["filepath.WalkDir\nrekursif dengan DirEntry"]

    style FP fill:#4f86c6,color:#fff
    style Build fill:#e8f5e9
    style Split fill:#e3f2fd
    style Convert fill:#fff3e0
    style Search fill:#f3e5f5
    style Walk fill:#fce4ec

filepath.Join — Membangun Path dengan Benar #

filepath.Join adalah fungsi yang paling sering digunakan dari package ini. Ia menggabungkan komponen path dengan separator yang tepat untuk sistem operasi saat ini, dan secara otomatis membersihkan path yang dihasilkan:

package main

import (
    "fmt"
    "path/filepath"
)

func main() {
    // ANTI-PATTERN: konkatenasi manual — rusak di Windows
    path1 := "/home/user" + "/" + "dokumen" + "/" + "file.txt"
    // Di Windows, ini menghasilkan path yang salah

    // BENAR: gunakan filepath.Join
    path2 := filepath.Join("/home/user", "dokumen", "file.txt")
    fmt.Println(path2)
    // Linux/macOS: /home/user/dokumen/file.txt
    // Windows:     \home\user\dokumen\file.txt

    // Join membersihkan path secara otomatis
    fmt.Println(filepath.Join("/home/user/", "/dokumen/../file.txt"))
    // /home/user/file.txt — bukan /home/user//dokumen/../file.txt

    // Join dengan nol atau satu argumen
    fmt.Println(filepath.Join())         // ""
    fmt.Println(filepath.Join("file"))   // "file"
    fmt.Println(filepath.Join("a", ""))  // "a"

    // Membangun path dari variabel
    homeDir := "/home/budi"
    appDir := filepath.Join(homeDir, ".config", "myapp")
    configFile := filepath.Join(appDir, "config.yaml")
    logFile := filepath.Join(appDir, "logs", "app.log")

    fmt.Println(appDir)    // /home/budi/.config/myapp
    fmt.Println(configFile) // /home/budi/.config/myapp/config.yaml
    fmt.Println(logFile)    // /home/budi/.config/myapp/logs/app.log

    // Join dengan path absolut di tengah — path sebelumnya diabaikan!
    fmt.Println(filepath.Join("/home", "/etc", "passwd"))
    // PERHATIAN: di beberapa implementasi, "/etc" bisa mengabaikan "/home"
    // Selalu gunakan path relatif untuk komponen tengah
}

Memecah Path — Dir, Base, Ext, Split #

path := "/home/budi/dokumen/laporan-2024.pdf"

// Dir — direktori (tanpa file)
fmt.Println(filepath.Dir(path))
// /home/budi/dokumen

// Base — nama file (dengan ekstensi)
fmt.Println(filepath.Base(path))
// laporan-2024.pdf

// Ext — ekstensi file (termasuk titik)
fmt.Println(filepath.Ext(path))
// .pdf

// Nama file tanpa ekstensi — tidak ada fungsi langsung, kombinasikan
nama := filepath.Base(path)
namaNoExt := nama[:len(nama)-len(filepath.Ext(nama))]
fmt.Println(namaNoExt)
// laporan-2024

// Split — kembalikan (dir, file) sekaligus
dir, file := filepath.Split(path)
fmt.Println(dir)  // /home/budi/dokumen/
fmt.Println(file) // laporan-2024.pdf
// CATATAN: dir menyertakan separator di akhir, file tidak

// Contoh dengan berbagai path
contoh := []string{
    "/home/budi/file.txt",
    "relative/path/file.go",
    "file.tar.gz",        // ekstensi ganda
    "/path/to/directory/", // direktori dengan trailing slash
    ".",
    "..",
    ".hidden",
    "",
}

for _, p := range contoh {
    fmt.Printf("%-30q dir=%-25q base=%-15q ext=%q\n",
        p, filepath.Dir(p), filepath.Base(p), filepath.Ext(p))
}
flowchart LR
    Path["'/home/budi/dokumen/laporan-2024.pdf'"] --> Dir["Dir()\n'/home/budi/dokumen'"]
    Path --> Base["Base()\n'laporan-2024.pdf'"]
    Path --> Ext["Ext()\n'.pdf'"]
    Path --> Split["Split()\ndir='/home/budi/dokumen/'\nfile='laporan-2024.pdf'"]
    Base --> NoExt["nama tanpa ext\n'laporan-2024'\n= Base - Ext"]

    style Path fill:#4f86c6,color:#fff
    style Dir fill:#e8f5e9
    style Base fill:#e3f2fd
    style Ext fill:#fff3e0
    style NoExt fill:#f3e5f5

filepath.Abs dan filepath.Rel — Path Absolut dan Relatif #

// Abs — konversi path relatif ke absolut berdasarkan working directory
absPath, err := filepath.Abs("config.yaml")
if err != nil {
    fmt.Println("error:", err)
    return
}
fmt.Println(absPath)
// /home/budi/myapp/config.yaml (jika CWD adalah /home/budi/myapp)

// Abs pada path yang sudah absolut — tidak berubah
absPath2, _ := filepath.Abs("/etc/hosts")
fmt.Println(absPath2) // /etc/hosts

// Abs juga membersihkan path
absPath3, _ := filepath.Abs("./config/../config.yaml")
fmt.Println(absPath3) // /home/budi/myapp/config.yaml

// Rel — path relatif dari basepath ke targetpath
rel, err := filepath.Rel("/home/budi", "/home/budi/dokumen/file.txt")
if err == nil {
    fmt.Println(rel) // dokumen/file.txt
}

rel2, _ := filepath.Rel("/home/budi/apps", "/home/budi/dokumen/file.txt")
fmt.Println(rel2) // ../dokumen/file.txt

// Rel untuk path di drive berbeda di Windows — error
// rel3, err := filepath.Rel("C:\\Users", "D:\\data")
// error: Rel: can't make D:\data relative to C:\Users

// Pola: temukan path relatif dari executable ke resource
execPath, _ := os.Executable()
execDir := filepath.Dir(execPath)
resourcePath := filepath.Join(execDir, "assets", "template.html")
fmt.Println(resourcePath)

filepath.Clean — Normalisasi Path #

filepath.Clean menormalisasi path dengan menerapkan aturan pembersihan:

// Clean menghapus elemen yang redundan
fmt.Println(filepath.Clean("/home//budi/./dokumen/../file.txt"))
// /home/budi/file.txt

fmt.Println(filepath.Clean("./config/./app/../main.go"))
// config/main.go

fmt.Println(filepath.Clean(""))
// . (string kosong → direktori saat ini)

fmt.Println(filepath.Clean("../../../etc/passwd"))
// ../../../etc/passwd — tidak aman! tetap diizinkan oleh Clean

// ANTI-PATTERN: asumsikan Clean mencegah path traversal
func bacaFile(base, userInput string) ([]byte, error) {
    path := filepath.Clean(filepath.Join(base, userInput))
    return os.ReadFile(path) // masih rentan path traversal!
}

// BENAR: validasi bahwa path ada di dalam direktori base
func bacaFileAman(base, userInput string) ([]byte, error) {
    // Pastikan base adalah path absolut
    absBase, err := filepath.Abs(base)
    if err != nil {
        return nil, err
    }

    // Gabungkan dan bersihkan
    fullPath := filepath.Clean(filepath.Join(absBase, userInput))

    // Validasi bahwa fullPath dimulai dengan absBase
    if !strings.HasPrefix(fullPath, absBase+string(filepath.Separator)) {
        return nil, fmt.Errorf("akses ditolak: path di luar direktori yang diizinkan")
    }

    return os.ReadFile(fullPath)
}

filepath.Glob — Mencari File dengan Wildcard #

filepath.Glob mencari semua file yang cocok dengan pola yang diberikan. Pattern yang didukung mirip dengan Unix shell globbing:

// Cari semua file .go di direktori saat ini
matches, err := filepath.Glob("*.go")
if err != nil {
    fmt.Println("error:", err)
    return
}
fmt.Println("File .go:", matches)
// [main.go handler.go service.go]

// Cari semua file di subdirektori (satu level)
matches2, _ := filepath.Glob("cmd/*")
fmt.Println(matches2)
// [cmd/main.go cmd/server.go]

// Pattern yang didukung:
// * — cocok dengan apapun kecuali separator
// ? — cocok dengan satu karakter apapun kecuali separator
// [abc] — cocok dengan satu karakter dalam set
// [a-z] — cocok dengan satu karakter dalam range

// Contoh pattern
matches3, _ := filepath.Glob("data/2024-0?.csv") // Januari-September 2024
matches4, _ := filepath.Glob("config.[yd]aml")   // .yaml atau .yml
matches5, _ := filepath.Glob("*.{go,mod}")        // TIDAK didukung! (bukan {})

// filepath.Glob tidak mendukung ** (recursive glob)
// Untuk rekursif, gunakan filepath.WalkDir

// Alternatif: WalkDir dengan filter ekstensi
func cariSemua(root, ext string) ([]string, error) {
    var hasil []string
    err := filepath.WalkDir(root, func(path string, d fs.DirEntry, err error) error {
        if err != nil {
            return err
        }
        if !d.IsDir() && filepath.Ext(path) == ext {
            hasil = append(hasil, path)
        }
        return nil
    })
    return hasil, err
}

// Penggunaan
fileGo, _ := cariSemua(".", ".go")
fmt.Printf("Ditemukan %d file .go\n", len(fileGo))

filepath.WalkDir — Traversal Direktori Rekursif #

filepath.WalkDir menelusuri seluruh struktur direktori secara rekursif dan memanggil callback untuk setiap file dan direktori yang ditemukan:

flowchart TD
    Root["/project"] --> WD["filepath.WalkDir(root, fn)"]

    WD --> Visit1["fn('/project', dir, nil)"]
    Visit1 --> Visit2["fn('/project/cmd', dir, nil)"]
    Visit2 --> Visit3["fn('/project/cmd/main.go', file, nil)"]
    Visit3 --> Visit4["fn('/project/internal', dir, nil)"]
    Visit4 --> Visit5["fn('/project/internal/handler.go', file, nil)"]
    Visit5 --> Visit6["fn('/project/go.mod', file, nil)"]
    Visit6 --> Visit7["fn('/project/go.sum', file, nil)"]

    subgraph Control["Kontrol Traversal"]
        C1["return nil\nlanjutkan"]
        C2["return fs.SkipDir\nskip direktori ini"]
        C3["return fs.SkipAll\nberhenti total (Go 1.20+)"]
        C4["return error\nberhenti dengan error"]
    end

    style Root fill:#4f86c6,color:#fff
    style Control fill:#e8f5e9
import (
    "io/fs"
    "path/filepath"
)

// Traversal dasar
func listSemua(root string) error {
    return filepath.WalkDir(root, func(path string, d fs.DirEntry, err error) error {
        if err != nil {
            // Handle error pada path tertentu (misalnya permission denied)
            fmt.Fprintf(os.Stderr, "error pada %s: %v\n", path, err)
            return nil // lanjutkan traversal
        }

        indent := strings.Repeat("  ", strings.Count(path, string(filepath.Separator)))
        tipe := "📄"
        if d.IsDir() {
            tipe = "📁"
        }
        fmt.Printf("%s%s %s\n", indent, tipe, d.Name())
        return nil
    })
}

// Skip direktori yang tidak perlu
func cariFileGo(root string) ([]string, error) {
    var files []string

    err := filepath.WalkDir(root, func(path string, d fs.DirEntry, err error) error {
        if err != nil {
            return nil // skip error, lanjutkan
        }

        // Skip direktori yang tidak perlu
        if d.IsDir() {
            nama := d.Name()
            if nama == ".git" || nama == "vendor" || nama == "node_modules" ||
                strings.HasPrefix(nama, ".") {
                return fs.SkipDir // skip direktori dan isinya
            }
            return nil
        }

        // Hanya ambil file .go
        if filepath.Ext(path) == ".go" {
            files = append(files, path)
        }
        return nil
    })

    return files, err
}

// Hitung ukuran total direktori
func ukuranDirektori(root string) (int64, error) {
    var total int64

    err := filepath.WalkDir(root, func(path string, d fs.DirEntry, err error) error {
        if err != nil || d.IsDir() {
            return nil
        }

        info, err := d.Info()
        if err != nil {
            return nil
        }

        total += info.Size()
        return nil
    })

    return total, err
}

// Temukan file terbaru di direktori
func fileTerbaru(root string) (string, time.Time, error) {
    var pathTerbaru string
    var waktuTerbaru time.Time

    err := filepath.WalkDir(root, func(path string, d fs.DirEntry, err error) error {
        if err != nil || d.IsDir() {
            return nil
        }

        info, err := d.Info()
        if err != nil {
            return nil
        }

        if info.ModTime().After(waktuTerbaru) {
            waktuTerbaru = info.ModTime()
            pathTerbaru = path
        }
        return nil
    })

    return pathTerbaru, waktuTerbaru, err
}

filepath.Match — Mencocokkan Pola #

// Match mencocokkan nama dengan pola glob
fmt.Println(filepath.Match("*.go", "main.go"))    // true, nil
fmt.Println(filepath.Match("*.go", "main.txt"))   // false, nil
fmt.Println(filepath.Match("*.go", "cmd/main.go")) // false, nil — * tidak cocok dengan /

fmt.Println(filepath.Match("?ello", "Hello")) // true, nil
fmt.Println(filepath.Match("?ello", "hello")) // true, nil
fmt.Println(filepath.Match("?ello", "ello"))  // false, nil

fmt.Println(filepath.Match("[hH]ello", "Hello")) // true, nil
fmt.Println(filepath.Match("[hH]ello", "hello")) // true, nil
fmt.Println(filepath.Match("[hH]ello", "Aello")) // false, nil

// Match untuk filter file dalam WalkDir
func filterFile(pattern, path string) bool {
    // Hanya cocokkan nama file, bukan path lengkap
    matched, err := filepath.Match(pattern, filepath.Base(path))
    return err == nil && matched
}

filepath.SplitList — Parsing PATH Environment #

// SplitList memisahkan PATH environment variable berdasarkan separator OS
// Linux/macOS: : (titik dua)
// Windows: ; (titik koma)

pathEnv := os.Getenv("PATH")
dirs := filepath.SplitList(pathEnv)

fmt.Printf("Ada %d direktori di PATH:\n", len(dirs))
for i, dir := range dirs {
    fmt.Printf("  %d. %s\n", i+1, dir)
}

// Cari executable di PATH
func cariExecutable(nama string) (string, error) {
    pathEnv := os.Getenv("PATH")
    dirs := filepath.SplitList(pathEnv)

    for _, dir := range dirs {
        path := filepath.Join(dir, nama)
        info, err := os.Stat(path)
        if err == nil && !info.IsDir() && info.Mode()&0111 != 0 {
            return path, nil
        }
    }
    return "", fmt.Errorf("%s: tidak ditemukan di PATH", nama)
}

namaGo, err := cariExecutable("go")
if err == nil {
    fmt.Println("Go ditemukan di:", namaGo)
}

// EvalSymlinks mengikuti symlink dan mengembalikan path nyata
realPath, err := filepath.EvalSymlinks("/usr/bin/python3")
if err == nil {
    fmt.Println("Path nyata:", realPath)
    // Mungkin: /usr/bin/python3.11
}

// Berguna untuk memastikan dua path yang berbeda menunjuk ke file yang sama
func samePath(path1, path2 string) (bool, error) {
    real1, err := filepath.EvalSymlinks(path1)
    if err != nil {
        return false, err
    }
    real2, err := filepath.EvalSymlinks(path2)
    if err != nil {
        return false, err
    }
    return real1 == real2, nil
}

filepath.FromSlash dan filepath.ToSlash #

// Konversi antara format URL (slash) dan format OS
// Berguna saat menerima path dari konfigurasi atau API yang menggunakan /

// FromSlash: '/' → separator OS
// Di Linux/macOS: tidak berubah
// Di Windows: '/' → '\'
winPath := filepath.FromSlash("home/user/dokumen/file.txt")
fmt.Println(winPath)
// Linux: home/user/dokumen/file.txt
// Windows: home\user\dokumen\file.txt

// ToSlash: separator OS → '/'
// Di Linux/macOS: tidak berubah
// Di Windows: '\' → '/'
unixPath := filepath.ToSlash(`home\user\dokumen\file.txt`)
fmt.Println(unixPath)
// home/user/dokumen/file.txt (di semua OS)

// Pola: simpan path di konfigurasi selalu dengan /
// konversi ke format OS saat digunakan

type Config struct {
    DataDir   string `yaml:"data_dir"`   // disimpan dengan /
    LogDir    string `yaml:"log_dir"`
}

func (c *Config) DataDirOS() string {
    return filepath.FromSlash(c.DataDir) // konversi saat dipakai
}

Pola Penggunaan di Produksi #

Manajemen Direktori Aplikasi #

type AppDirs struct {
    Config string
    Data   string
    Log    string
    Cache  string
    Temp   string
}

func setupDirektoriApp(namaApp string) (*AppDirs, error) {
    // Dapatkan home directory user
    homeDir, err := os.UserHomeDir()
    if err != nil {
        return nil, fmt.Errorf("gagal dapatkan home dir: %w", err)
    }

    // Dapatkan direktori config yang sesuai per OS
    configDir, err := os.UserConfigDir()
    if err != nil {
        configDir = filepath.Join(homeDir, ".config")
    }

    // Dapatkan direktori cache
    cacheDir, err := os.UserCacheDir()
    if err != nil {
        cacheDir = filepath.Join(homeDir, ".cache")
    }

    dirs := &AppDirs{
        Config: filepath.Join(configDir, namaApp),
        Data:   filepath.Join(homeDir, ".local", "share", namaApp),
        Log:    filepath.Join(homeDir, ".local", "share", namaApp, "logs"),
        Cache:  filepath.Join(cacheDir, namaApp),
        Temp:   filepath.Join(os.TempDir(), namaApp),
    }

    // Buat semua direktori yang belum ada
    for _, dir := range []string{dirs.Config, dirs.Data, dirs.Log, dirs.Cache, dirs.Temp} {
        if err := os.MkdirAll(dir, 0755); err != nil {
            return nil, fmt.Errorf("buat direktori %s: %w", dir, err)
        }
    }

    return dirs, nil
}

Scanner Proyek — Analisis Struktur Kode #

type InfoProyek struct {
    TotalFile    int
    TotalBaris   int
    PerEkstensi  map[string]int
    FileIgnored  int
}

func analisisProyek(root string) (*InfoProyek, error) {
    info := &InfoProyek{
        PerEkstensi: make(map[string]int),
    }

    // Pola direktori yang diabaikan
    ignoreDirs := map[string]bool{
        ".git": true, "vendor": true, "node_modules": true,
        ".idea": true, ".vscode": true, "dist": true, "build": true,
    }

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

        if d.IsDir() {
            if ignoreDirs[d.Name()] {
                info.FileIgnored++
                return fs.SkipDir
            }
            return nil
        }

        ext := strings.ToLower(filepath.Ext(path))
        if ext == "" {
            ext = "(tanpa ekstensi)"
        }
        info.PerEkstensi[ext]++
        info.TotalFile++

        // Hitung baris untuk file teks
        if ext == ".go" || ext == ".js" || ext == ".py" || ext == ".ts" {
            n, _ := hitungBaris(path)
            info.TotalBaris += n
        }

        return nil
    })

    return info, err
}

func hitungBaris(path string) (int, error) {
    f, err := os.Open(path)
    if err != nil {
        return 0, err
    }
    defer f.Close()

    scanner := bufio.NewScanner(f)
    n := 0
    for scanner.Scan() {
        n++
    }
    return n, scanner.Err()
}

Backup dengan Preservasi Struktur #

func backupDirektori(src, dst string) error {
    // Normalisasi path
    src = filepath.Clean(src)
    dst = filepath.Clean(dst)

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

        // Hitung path relatif dari src
        relPath, err := filepath.Rel(src, path)
        if err != nil {
            return fmt.Errorf("rel path: %w", err)
        }

        // Path tujuan
        dstPath := filepath.Join(dst, relPath)

        if d.IsDir() {
            // Buat direktori di tujuan
            info, err := d.Info()
            if err != nil {
                return err
            }
            return os.MkdirAll(dstPath, info.Mode())
        }

        // Salin file
        return salinFile(path, dstPath)
    })
}

func salinFile(src, dst string) error {
    // Pastikan direktori tujuan ada
    if err := os.MkdirAll(filepath.Dir(dst), 0755); err != nil {
        return err
    }

    sumber, err := os.Open(src)
    if err != nil {
        return err
    }
    defer sumber.Close()

    tujuan, err := os.Create(dst)
    if err != nil {
        return err
    }
    defer tujuan.Close()

    _, err = io.Copy(tujuan, sumber)
    return err
}

filepath vs path — Perbedaan Penting #

flowchart LR
    subgraph FilePath["path/filepath\n(untuk path filesystem)"]
        F1["Menggunakan separator OS\nLinux/Mac: /\nWindows: \\"]
        F2["Untuk path ke file\ndan direktori di disk"]
        F3["filepath.Join\nfilepath.Dir\nfilepath.WalkDir"]
    end

    subgraph Path["path\n(untuk URL path)"]
        P1["Selalu menggunakan /\ntidak peduli OS"]
        P2["Untuk URL path, HTTP path\nbukan filesystem"]
        P3["path.Join\npath.Dir\npath.Base"]
    end

    subgraph When["Gunakan yang mana?"]
        W1["File di disk → filepath"]
        W2["URL, HTTP route → path"]
        W3["go:embed path → path"]
        W4["os.Open, os.ReadFile → filepath"]
    end

    style FilePath fill:#e8f5e9
    style Path fill:#e3f2fd
    style When fill:#fff3e0
import (
    "path"
    "path/filepath"
)

// filepath — untuk sistem file
configPath := filepath.Join(homeDir, ".config", "app.yaml")
os.ReadFile(configPath) // benar

// path — untuk URL atau HTTP route
urlPath := path.Join("/api", "v1", "users")
// selalu: /api/v1/users (bukan \api\v1\users di Windows)

// ANTI-PATTERN: gunakan filepath untuk URL
badURL := filepath.Join("/api", "v1", "users")
// Di Windows: \api\v1\users — salah untuk HTTP!

// ANTI-PATTERN: gunakan path untuk filesystem di Windows
badPath := path.Join("C:", "Users", "file.txt")
// Hasil: C:/Users/file.txt — mungkin salah di Windows

Kapan Beralih ke Alternatif #

Tetap gunakan path/filepath jika:
  ✓ Semua operasi path filesystem: Join, Dir, Base, Ext
  ✓ Traversal direktori dengan WalkDir
  ✓ Pencarian file dengan Glob
  ✓ Konversi path relatif-absolut dengan Abs dan Rel
  ✓ Kode yang harus berjalan lintas platform (Windows, Linux, macOS)

Gunakan package path (bukan filepath) jika:
  ✗ Bekerja dengan URL path atau HTTP route
  ✗ Path dalam go:embed directive
  ✗ Manipulasi path yang selalu menggunakan / tanpa peduli OS

Pertimbangkan os.DirFS / fs.FS jika:
  ✗ Abstraksi filesystem yang bisa di-mock di testing
  ✗ Bekerja dengan embedded files (go:embed)
  ✗ Virtual filesystem atau filesystem kustom

Pertimbangkan library eksternal jika:
  ✗ Watching perubahan file → github.com/fsnotify/fsnotify
  ✗ Glob pattern yang lebih canggih (** untuk recursive)
     → github.com/bmatcuk/doublestar
  ✗ Virtual filesystem yang lebih lengkap → github.com/spf13/afero

Ringkasan #

  • filepath.Join selalu lebih baik dari konkatenasi string — ia menggunakan separator yang tepat untuk OS saat ini dan membersihkan path yang dihasilkan secara otomatis.
  • filepath.Abs untuk mengkonversi path relatif ke absolut berdasarkan working directory saat ini — berguna untuk validasi dan logging path yang jelas.
  • filepath.Dir, filepath.Base, filepath.Ext untuk memecah path — nama file tanpa ekstensi tidak ada fungsi langsungnya, kombinasikan: Base[:len(Base)-len(Ext)].
  • filepath.WalkDir lebih efisien dari filepath.Walk — ia tidak memanggil Lstat ekstra untuk setiap entry, gunakan WalkDir untuk traversal di Go 1.16+.
  • Kembalikan fs.SkipDir dari callback WalkDir untuk melewati direktori beserta isinya — gunakan untuk skip .git, vendor, node_modules, dan direktori lain yang tidak perlu.
  • filepath.Glob tidak mendukung ** (recursive glob) — untuk pencarian rekursif, gunakan WalkDir dengan filter ekstensi.
  • filepath.Clean tidak mencegah path traversal — selalu validasi bahwa path yang dihasilkan dimulai dengan base directory yang diizinkan setelah Clean.
  • Gunakan path (bukan filepath) untuk URLfilepath.Join di Windows menghasilkan backslash yang salah untuk URL dan HTTP route.
  • filepath.Rel untuk membuat path relatif — berguna saat membuat backup, laporan, atau menampilkan path yang lebih pendek kepada pengguna.

← Sebelumnya: Bufio   Berikutnya: Regexp →

About | Author | Content Scope | Editorial Policy | Privacy Policy | Disclaimer | Contact