Aplikasi jarang bekerja dengan satu berkas saja. Program backup harus menelusuri pohon folder, alat analisis memeriksa ukuran dan jenis setiap entri, sedangkan cache perlu membuat struktur direktori bersarang dengan cepat. Package os menutup semua kebutuhan itu: os.ReadDir untuk isi direktori, os.Stat untuk metadata, os.MkdirAll dan os.Remove untuk membangun serta membongkar struktur, dan filepath.WalkDir untuk penjelajahan penuh. Kita juga berkenalan dengan fstest.MapFS, filesystem virtual dalam memori yang membuat demo deterministik.
Mendaftar Isi Direktori dengan os.ReadDir #
Fungsi os.ReadDir(dir) membaca isi satu direktori dan mengembalikan []os.DirEntry yang sudah terurut berdasarkan nama — urutan yang dijamin stabil sehingga output program tidak perlu diurutkan lagi. Setiap entri membawa method Name() untuk nama, IsDir() untuk membedakan direktori dari berkas, dan Info() yang mengembalikan fs.FileInfo lengkap:
entries, err := os.ReadDir("dokumen")
if err != nil {
fmt.Println("gagal membaca direktori:", err)
return
}
for _, e := range entries {
fmt.Println(e.Name(), e.IsDir())
}Info() pada os.DirEntry bersifat malas: metadata hanya dibaca bila diminta, sehingga menelusuri ribuan entri tidak memicu syscall stat per entri.
Membaca Metadata dengan os.Stat #
Fungsi os.Stat(path) mengambil metadata satu entri dalam bentuk fs.FileInfo. Interface tersebut menyediakan Name(), Size() dalam byte, Mode(), IsDir(), dan ModTime():
info, err := os.Stat("dokumen/catatan.txt")
if err != nil {
fmt.Println("gagal membaca metadata:", err)
return
}
fmt.Println(info.Size(), info.IsDir())Saudara kembarnya, os.Lstat, berperilaku identik kecuali pada satu kasus: tautan simbolik. os.Stat mengikuti tautan dan melaporkan metadata berkas tujuan, sedangkan os.Lstat melaporkan metadata tautannya sendiri. Bit tautan dapat diuji dengan info.Mode()&fs.ModeSymlink != 0. Perbedaan ini penting bagi alat yang tidak boleh mengikuti tautan, misalnya program backup.
Membangun dan Membongkar Struktur #
os.Mkdir membuat satu direktori dan gagal bila induknya belum ada. os.MkdirAll membuat seluruh jalur sekaligus — MkdirAll("dokumen/laporan/2026") membuat tiga level tanpa komplain bila sebagian sudah ada. Untuk pembongkaran, os.Remove menghapus satu berkas atau direktori kosong, sedangkan os.RemoveAll memusnahkan pohon direktori beserta seluruh isinya tanpa bertanya.
Menelusuri Pohon dengan WalkDir #
filepath.WalkDir(akar, fn) mengunjungi akar, lalu setiap entri di bawahnya secara berurutan leksikal. Argumen kedua adalah fungsi callback yang menerima path, fs.DirEntry, dan error; mengembalikan fs.SkipDir membuat penjelajahan melompati satu direktori, sedangkan error menghentikan seluruh penjelajahan.
Untuk pengujian dan demonstrasi, testing/fstest menyediakan fstest.MapFS — filesystem virtual yang dibangun dari map path ke isi berkas. Direktori tersintesis otomatis dari jalur yang ada, dan nilai ini mengimplementasikan fs.FS sehingga bisa langsung ditelusuri dengan fs.WalkDir:
peta := fstest.MapFS{
"proyek/README.md": {Data: []byte("demo\n")},
"proyek/util/util.go": {Data: []byte("package util\n")},
}
fs.WalkDir(peta, ".", func(path string, d fs.DirEntry, err error) error {
fmt.Println(path, d.IsDir())
return nil
})Pola fstest.MapFS dipakai luas dalam pengujian karena struktur pohonnya pasti identik di setiap mesin. Chapter Test Fixture
menunjukkan pemakaiannya pada berkas _test.go.
Program Utuh #
Berkas contoh.go merangkai seluruh materi di atas: membangun struktur dokumen/laporan, mendaftar entri, membandingkan Stat dengan Lstat pada sebuah tautan simbolik, menghapus satu berkas, menelusuri pohon di disk dengan filepath.WalkDir, lalu menelusuri fstest.MapFS dengan fs.WalkDir:
package main
import (
"errors"
"fmt"
"io/fs"
"os"
"path/filepath"
"testing/fstest"
)
func main() {
// Direktori kerja sementara agar program aman dijalankan berulang.
dir, err := os.MkdirTemp("", "contoh-dir-*")
if err != nil {
fmt.Println("gagal membuat direktori:", err)
return
}
defer os.RemoveAll(dir)
// Struktur uji: MkdirAll membuat seluruh jalur bersarang sekaligus.
if err := os.MkdirAll(filepath.Join(dir, "dokumen", "laporan"), 0755); err != nil {
fmt.Println("gagal membuat direktori:", err)
return
}
if err := buatBerkas(filepath.Join(dir, "baca-saya.txt"), "mulai dari sini\n"); err != nil {
fmt.Println("gagal membuat berkas:", err)
return
}
if err := buatBerkas(filepath.Join(dir, "dokumen", "catatan.txt"), "catatan harian\n"); err != nil {
fmt.Println("gagal membuat berkas:", err)
return
}
if err := buatBerkas(filepath.Join(dir, "dokumen", "laporan", "penjualan.txt"), "omzet: 1000000\n"); err != nil {
fmt.Println("gagal membuat berkas:", err)
return
}
// os.ReadDir mengembalikan daftar entri terurut berdasarkan nama.
fmt.Println("isi direktori utama:")
entries, err := os.ReadDir(dir)
if err != nil {
fmt.Println("gagal membaca direktori:", err)
return
}
for _, e := range entries {
jenis := "berkas"
if e.IsDir() {
jenis = "direktori"
}
fmt.Printf(" %-14s %s\n", e.Name(), jenis)
}
// os.Stat memberikan metadata satu entri.
target := filepath.Join(dir, "dokumen", "catatan.txt")
info, err := os.Stat(target)
if err != nil {
fmt.Println("gagal membaca metadata:", err)
return
}
fmt.Printf("Stat catatan.txt : %d byte, isDir=%v\n", info.Size(), info.IsDir())
// Stat mengikuti tautan simbolik; Lstat melaporkan tautannya sendiri.
if err := os.Symlink(target, filepath.Join(dir, "pintasan.txt")); err != nil {
fmt.Println("gagal membuat tautan:", err)
return
}
infoStat, err := os.Stat(filepath.Join(dir, "pintasan.txt"))
if err != nil {
fmt.Println("gagal membaca metadata:", err)
return
}
infoLstat, err := os.Lstat(filepath.Join(dir, "pintasan.txt"))
if err != nil {
fmt.Println("gagal membaca metadata:", err)
return
}
fmt.Printf("Stat pintasan : %d byte (mengikuti tautan)\n", infoStat.Size())
fmt.Printf("Lstat pintasan : symlink=%v\n", infoLstat.Mode()&fs.ModeSymlink != 0)
// os.Remove menghapus satu entri.
if err := os.Remove(filepath.Join(dir, "baca-saya.txt")); err != nil {
fmt.Println("gagal menghapus berkas:", err)
return
}
_, err = os.Stat(filepath.Join(dir, "baca-saya.txt"))
fmt.Println("setelah Remove :", errors.Is(err, fs.ErrNotExist))
// filepath.WalkDir menelusuri seluruh pohon secara leksikal.
fmt.Println("pohon di disk:")
err = filepath.WalkDir(dir, func(path string, d fs.DirEntry, err error) error {
if err != nil {
return err
}
rel, err := filepath.Rel(dir, path)
if err != nil {
return err
}
if rel == "." {
return nil
}
jenis := "berkas"
if d.IsDir() {
jenis = "direktori"
}
fmt.Printf(" %-30s %s\n", rel, jenis)
return nil
})
if err != nil {
fmt.Println("gagal menelusuri pohon:", err)
return
}
// fstest.MapFS adalah filesystem virtual dalam memori.
peta := fstest.MapFS{
"proyek/README.md": {Data: []byte("demo fstest\n")},
"proyek/main.go": {Data: []byte("package main\n")},
"proyek/util/util.go": {Data: []byte("package util\n")},
}
fmt.Println("pohon di fstest.MapFS:")
err = fs.WalkDir(peta, ".", func(path string, d fs.DirEntry, err error) error {
if err != nil {
return err
}
if path == "." {
return nil
}
jenis := "berkas"
if d.IsDir() {
jenis = "direktori"
}
fmt.Printf(" %-30s %s\n", path, jenis)
return nil
})
if err != nil {
fmt.Println("gagal menelusuri pohon:", err)
return
}
}
// buatBerkas membuat berkas kecil untuk keperluan demo.
func buatBerkas(path, isi string) error {
return os.WriteFile(path, []byte(isi), 0644)
}Jalankan program dari direktori chapter dengan perintah berikut:
go run contoh.goOutput yang Dihasilkan:
isi direktori utama:
baca-saya.txt berkas
dokumen direktori
Stat catatan.txt : 15 byte, isDir=false
Stat pintasan : 15 byte (mengikuti tautan)
Lstat pintasan : symlink=true
setelah Remove : true
pohon di disk:
dokumen direktori
dokumen/catatan.txt berkas
dokumen/laporan direktori
dokumen/laporan/penjualan.txt berkas
pintasan.txt berkas
pohon di fstest.MapFS:
proyek direktori
proyek/README.md berkas
proyek/main.go berkas
proyek/util direktori
proyek/util/util.go berkasMari kita telaah baris demi baris program di atas:
- Baris 22-37 (
MkdirAll+buatBerkas): satu panggilanMkdirAllmembuatdokumen/laporandua level sekaligus dengan permission0755; tiga berkas uji lalu diisi lewat helperbuatBerkasyang membungkusos.WriteFile. - Baris 41-52 (
os.ReadDir): hasil[]os.DirEntryterurut nama (baca-saya.txtsebelumdokumen), dane.IsDir()membedakan jenis tanpa membaca metadata tambahan. - Baris 55-61 (
os.Stat): metadatacatatan.txtterbaca 15 byte dan bukan direktori;ModTimesengaja tidak dicetak agar output stabil antar run. - Baris 64-79 (
Symlink+StatvsLstat):Statpadapintasan.txtmelaporkan 15 byte milik berkas tujuan karena tautan diikuti;Lstatpada entri yang sama melaporkan bitfs.ModeSymlinkkarena tautannya sendiri yang diperiksa. - Baris 82-87 (
os.Remove): berkas dihapus, laluos.Statpada path yang sama gagal —errors.Is(err, fs.ErrNotExist)mengembalikantrue. - Baris 89-113 dan 116-133 (kedua
WalkDir): penelusuran disk mencetak path relatif (lewatfilepath.Rel) agar path sementara acak tidak muncul di output; penelusuranfstest.MapFSmemakaifs.WalkDirgenerik pada antarmukafs.FS.
Struktur direktori kini bisa dibangun, dijelajahi, dan dibongkar. Satu properti entri yang tadi hanya lewat di permukaan — info.Mode() dengan notasi -rw-r--r-- — menyimpan seluruh aturan siapa boleh melakukan apa terhadap sebuah berkas. Chapter File Permissions
membedah notasi oktal tersebut sampai tuntas.
Referensi #
- Dokumentasi os: ReadDir — kontrak daftar entri dan jaminan urutannya.
- Dokumentasi io/fs: WalkDir
— aturan penelusuran pohon dan
fs.SkipDir. - Dokumentasi testing/fstest
—
MapFSsebagai filesystem virtual.
Berkas Sumber
Berkas pendukung halaman ini dapat diunduh dan dijalankan langsung sesuai penjelasan di atas.
contoh.go
Unduh berkaspackage main
import (
"errors"
"fmt"
"io/fs"
"os"
"path/filepath"
"testing/fstest"
)
func main() {
// Direktori kerja sementara agar program aman dijalankan berulang.
dir, err := os.MkdirTemp("", "contoh-dir-*")
if err != nil {
fmt.Println("gagal membuat direktori:", err)
return
}
defer os.RemoveAll(dir)
// Struktur uji: MkdirAll membuat seluruh jalur bersarang sekaligus.
if err := os.MkdirAll(filepath.Join(dir, "dokumen", "laporan"), 0755); err != nil {
fmt.Println("gagal membuat direktori:", err)
return
}
if err := buatBerkas(filepath.Join(dir, "baca-saya.txt"), "mulai dari sini\n"); err != nil {
fmt.Println("gagal membuat berkas:", err)
return
}
if err := buatBerkas(filepath.Join(dir, "dokumen", "catatan.txt"), "catatan harian\n"); err != nil {
fmt.Println("gagal membuat berkas:", err)
return
}
if err := buatBerkas(filepath.Join(dir, "dokumen", "laporan", "penjualan.txt"), "omzet: 1000000\n"); err != nil {
fmt.Println("gagal membuat berkas:", err)
return
}
// os.ReadDir mengembalikan daftar entri terurut berdasarkan nama.
fmt.Println("isi direktori utama:")
entries, err := os.ReadDir(dir)
if err != nil {
fmt.Println("gagal membaca direktori:", err)
return
}
for _, e := range entries {
jenis := "berkas"
if e.IsDir() {
jenis = "direktori"
}
fmt.Printf(" %-14s %s\n", e.Name(), jenis)
}
// os.Stat memberikan metadata satu entri.
target := filepath.Join(dir, "dokumen", "catatan.txt")
info, err := os.Stat(target)
if err != nil {
fmt.Println("gagal membaca metadata:", err)
return
}
fmt.Printf("Stat catatan.txt : %d byte, isDir=%v\n", info.Size(), info.IsDir())
// Stat mengikuti tautan simbolik; Lstat melaporkan tautannya sendiri.
if err := os.Symlink(target, filepath.Join(dir, "pintasan.txt")); err != nil {
fmt.Println("gagal membuat tautan:", err)
return
}
infoStat, err := os.Stat(filepath.Join(dir, "pintasan.txt"))
if err != nil {
fmt.Println("gagal membaca metadata:", err)
return
}
infoLstat, err := os.Lstat(filepath.Join(dir, "pintasan.txt"))
if err != nil {
fmt.Println("gagal membaca metadata:", err)
return
}
fmt.Printf("Stat pintasan : %d byte (mengikuti tautan)\n", infoStat.Size())
fmt.Printf("Lstat pintasan : symlink=%v\n", infoLstat.Mode()&fs.ModeSymlink != 0)
// os.Remove menghapus satu entri.
if err := os.Remove(filepath.Join(dir, "baca-saya.txt")); err != nil {
fmt.Println("gagal menghapus berkas:", err)
return
}
_, err = os.Stat(filepath.Join(dir, "baca-saya.txt"))
fmt.Println("setelah Remove :", errors.Is(err, fs.ErrNotExist))
// filepath.WalkDir menelusuri seluruh pohon secara leksikal.
fmt.Println("pohon di disk:")
err = filepath.WalkDir(dir, func(path string, d fs.DirEntry, err error) error {
if err != nil {
return err
}
rel, err := filepath.Rel(dir, path)
if err != nil {
return err
}
if rel == "." {
return nil
}
jenis := "berkas"
if d.IsDir() {
jenis = "direktori"
}
fmt.Printf(" %-30s %s\n", rel, jenis)
return nil
})
if err != nil {
fmt.Println("gagal menelusuri pohon:", err)
return
}
// fstest.MapFS adalah filesystem virtual dalam memori.
peta := fstest.MapFS{
"proyek/README.md": {Data: []byte("demo fstest\n")},
"proyek/main.go": {Data: []byte("package main\n")},
"proyek/util/util.go": {Data: []byte("package util\n")},
}
fmt.Println("pohon di fstest.MapFS:")
err = fs.WalkDir(peta, ".", func(path string, d fs.DirEntry, err error) error {
if err != nil {
return err
}
if path == "." {
return nil
}
jenis := "berkas"
if d.IsDir() {
jenis = "direktori"
}
fmt.Printf(" %-30s %s\n", path, jenis)
return nil
})
if err != nil {
fmt.Println("gagal menelusuri pohon:", err)
return
}
}
// buatBerkas membuat berkas kecil untuk keperluan demo.
func buatBerkas(path, isi string) error {
return os.WriteFile(path, []byte(isi), 0644)
}