Program sering membutuhkan ruang kerja sekali pakai: hasil parsial sebelum digabung, data perantara pemrosesan besar, atau draf yang akan menggantikan berkas final. Menamai berkas sementara sendiri seperti "temp.txt" adalah resep tabrakan — dua proses bersamaan akan saling menimpa isinya. Package os menyelesaikannya dengan os.CreateTemp dan os.MkdirTemp, dua fungsi yang menjamin nama unik lewat string acak. Pada chapter ini kita juga mempelajari pola tulis atomik: penulisan ke berkas sementara diikuti os.Rename, teknik yang membuat pembaca tidak pernah melihat berkas setengah jadi.
Berkas Sementara dengan os.CreateTemp #
Fungsi os.CreateTemp(dir, pattern) membuat satu berkas baru dan langsung membukanya untuk ditulis. Argumen pertama adalah direktori tujuan — string kosong berarti direktori sementara sistem (/tmp di Linux). Argumen kedua adalah pola nama:
tmp, err := os.CreateTemp("", "catatan-*.txt")
if err != nil {
fmt.Println("gagal membuat berkas sementara:", err)
return
}
defer os.Remove(tmp.Name())Tanda * pada pola diganti dengan angka acak, sehingga pola catatan-*.txt menghasilkan nama seperti catatan-3918262731.txt. Tanpa tanda *, angka acak menempel di belakang pola. Fungsi tersebut mencoba berkali-kali sampai menemukan nama yang belum dipakai, sehingga dua program paralel tidak berakhir di berkas yang sama.
Berbeda dengan os.Create, berkas dibuka dengan mode read-write dan mengembalikan handle *os.File biasa — method Write, Read, dan Sync bekerja normal; nama acaknya hanya terlihat lewat tmp.Name().
Direktori Sementara dengan os.MkdirTemp #
Saudara kandungnya, os.MkdirTemp(dir, pattern), bekerja untuk direktori dengan mekanisme nama acak yang sama:
dir, err := os.MkdirTemp("", "pekerjaan-*")
if err != nil {
fmt.Println("gagal membuat direktori:", err)
return
}
defer os.RemoveAll(dir)Direktori sementara cocok dijadikan akar untuk banyak berkas kerja — seluruh isi anak-anaknya bisa dibersihkan dengan satu os.RemoveAll di akhir fungsi. Kombinasi MkdirTemp + defer os.RemoveAll dipakai setiap contoh program pada section ini agar aman dijalankan berulang.
Untuk kode pengujian, framework testing menyediakan jalan yang lebih ringkas: t.TempDir() membuat direktori sementara yang dibersihkan otomatis saat test selesai. Pemakaiannya dibahas pada chapter Test Fixture
.
Pola Tulis Atomik #
Penggunaan terpenting berkas sementara adalah penulisan atomik: mengganti isi berkas tanpa risiko pembaca melihat keadaan parsial. Penulisan langsung ke berkas final membuka jendela ketika berkas terpotong setengah jadi — jika program crash di tengah penulisan, berkas final rusak permanen. Pola yang aman memiliki empat langkah:
1. buat berkas sementara di direktori yang sama dengan berkas final
2. tulis seluruh data ke berkas sementara
3. tutup berkas, (opsional) Sync agar data turun ke disk
4. os.Rename(berkas sementara, berkas final)Langkah keempat adalah kuncinya: pada sistem berkas yang sama, rename adalah operasi atomik di kernel — nama berkas lama berganti ke isi baru sekaligus, tanpa keadaan antara. Pembaca yang datang tepat saat pergantian akan melihat isi lama yang utuh atau isi baru yang utuh, tidak pernah campuran. Berkas sementara wajib berada di direktori yang sama dengan targetnya karena rename lintas sistem berkas dijalankan sebagai salin-then-rename yang tidak atomik.
Program Utuh #
Berkas contoh.go membuat berkas sementara, membaca kembali isinya, lalu memakai fungsi tulisAtomik untuk mengganti data.txt secara atomik. Nama sementara yang acak tidak pernah dicetak; hanya status, isi, dan ukuran yang dilaporkan:
package main
import (
"fmt"
"os"
"path/filepath"
)
func main() {
// Direktori sementara dengan nama acak untuk semua berkas kerja.
dir, err := os.MkdirTemp("", "contoh-temp-*")
if err != nil {
fmt.Println("gagal membuat direktori:", err)
return
}
defer os.RemoveAll(dir)
// CreateTemp mengganti tanda * dengan string acak agar nama unik.
tmp, err := os.CreateTemp(dir, "catatan-*.txt")
if err != nil {
fmt.Println("gagal membuat berkas sementara:", err)
return
}
defer os.Remove(tmp.Name()) // cadangan; RemoveAll sudah menyapu bersih
isi := "laporan perantara Budi"
if _, err := tmp.WriteString(isi); err != nil {
fmt.Println("gagal menulis:", err)
tmp.Close()
return
}
if err := tmp.Close(); err != nil {
fmt.Println("gagal menutup berkas:", err)
return
}
data, err := os.ReadFile(tmp.Name())
if err != nil {
fmt.Println("gagal membaca berkas sementara:", err)
return
}
fmt.Println("berkas sementara : dibuat")
fmt.Printf("isi sementara : %s (%d byte)\n", data, len(data))
// Pola tulis atomik: tulis ke berkas sementara, lalu rename ke nama final.
akhir := filepath.Join(dir, "data.txt")
if err := tulisAtomik(akhir, []byte("data final Siti")); err != nil {
fmt.Println("gagal menulis atomik:", err)
return
}
data, err = os.ReadFile(akhir)
if err != nil {
fmt.Println("gagal membaca berkas final:", err)
return
}
fmt.Println("rename atomik : berhasil")
fmt.Printf("isi data.txt : %s (%d byte)\n", data, len(data))
}
// tulisAtomik menulis data ke berkas sementara lalu mengganti nama berkas
// final sekaligus, sehingga pembaca tidak pernah melihat isi parsial.
func tulisAtomik(path string, data []byte) error {
dir := filepath.Dir(path)
base := filepath.Base(path)
tmp, err := os.CreateTemp(dir, base+".tmp-*")
if err != nil {
return err
}
sementara := tmp.Name()
if _, err := tmp.Write(data); err != nil {
tmp.Close()
os.Remove(sementara)
return err
}
// Sync memaksa data turun ke media penyimpanan sebelum rename.
if err := tmp.Sync(); err != nil {
tmp.Close()
os.Remove(sementara)
return err
}
if err := tmp.Close(); err != nil {
os.Remove(sementara)
return err
}
if err := os.Rename(sementara, path); err != nil {
os.Remove(sementara)
return err
}
return nil
}Jalankan program dari direktori chapter dengan perintah berikut:
go run contoh.goOutput yang Dihasilkan:
berkas sementara : dibuat
isi sementara : laporan perantara Budi (22 byte)
rename atomik : berhasil
isi data.txt : data final Siti (15 byte)Mari kita telaah baris demi baris program di atas:
- Baris 11-16 (
os.MkdirTemp+defer os.RemoveAll): direktori kerja acak menjadi akar semua berkas sementara; satudeferdi akhirmainmenyapu semuanya, membuat program aman dijalankan berulang. - Baris 19-24 (
os.CreateTempdengan polacatatan-*.txt): tanda*diganti angka acak sehingga nama unik;defer os.Remove(tmp.Name())dijadwalkan segera setelahnya sebagai cadangan pembersihan. - Baris 37-43 (
os.ReadFilepada berkas sementara): isi dibaca kembali lalu dicetak — hanya isi dan ukurannya, nama acaknya tidak pernah masuk output agar hasil run stabil. - Baris 46-57 (
tulisAtomikdarimain):data.txtditulis lewat pola atomik, lalu dibaca kembali untuk membuktikan isi finalnya sudah utuh. - Baris 66-70 (
os.CreateTempdi dalamtulisAtomik): berkas sementara dibuat di direktori yang sama dengan target (filepath.Dir(path)) — syarat agaros.Renametetap operasi atomik dalam satu sistem berkas. - Baris 72-91 (tulis,
Sync,Close,Rename): data ditulis penuh,Syncmemaksa turun ke disk,Closemelepas handle, danRenamemengganti nama sekaligus; setiap cabang gagal memanggilos.Remove(sementara)agar tidak ada berkas liar tertinggal.
Berkas sementara dengan nama unik plus rename atomik memberi fondasi untuk penulisan yang aman. Namun semua contoh sejauh ini memuat data seluruhnya di memori sebelum diproses. Chapter Streaming Data
memperkenalkan io.Copy dan rangkaian reader yang memproses data sambil mengalir — tanpa pernah menampung semuanya.
Referensi #
- Dokumentasi os: CreateTemp
— aturan pola nama dan penggantian tanda
*. - Dokumentasi testing: T.TempDir — direktori sementara otomatis untuk pengujian.
- Dokumentasi os: Rename — jaminan atomik penggantian nama dalam satu sistem berkas.
Berkas Sumber
Berkas pendukung halaman ini dapat diunduh dan dijalankan langsung sesuai penjelasan di atas.
contoh.go
Unduh berkaspackage main
import (
"fmt"
"os"
"path/filepath"
)
func main() {
// Direktori sementara dengan nama acak untuk semua berkas kerja.
dir, err := os.MkdirTemp("", "contoh-temp-*")
if err != nil {
fmt.Println("gagal membuat direktori:", err)
return
}
defer os.RemoveAll(dir)
// CreateTemp mengganti tanda * dengan string acak agar nama unik.
tmp, err := os.CreateTemp(dir, "catatan-*.txt")
if err != nil {
fmt.Println("gagal membuat berkas sementara:", err)
return
}
defer os.Remove(tmp.Name()) // cadangan; RemoveAll sudah menyapu bersih
isi := "laporan perantara Budi"
if _, err := tmp.WriteString(isi); err != nil {
fmt.Println("gagal menulis:", err)
tmp.Close()
return
}
if err := tmp.Close(); err != nil {
fmt.Println("gagal menutup berkas:", err)
return
}
data, err := os.ReadFile(tmp.Name())
if err != nil {
fmt.Println("gagal membaca berkas sementara:", err)
return
}
fmt.Println("berkas sementara : dibuat")
fmt.Printf("isi sementara : %s (%d byte)\n", data, len(data))
// Pola tulis atomik: tulis ke berkas sementara, lalu rename ke nama final.
akhir := filepath.Join(dir, "data.txt")
if err := tulisAtomik(akhir, []byte("data final Siti")); err != nil {
fmt.Println("gagal menulis atomik:", err)
return
}
data, err = os.ReadFile(akhir)
if err != nil {
fmt.Println("gagal membaca berkas final:", err)
return
}
fmt.Println("rename atomik : berhasil")
fmt.Printf("isi data.txt : %s (%d byte)\n", data, len(data))
}
// tulisAtomik menulis data ke berkas sementara lalu mengganti nama berkas
// final sekaligus, sehingga pembaca tidak pernah melihat isi parsial.
func tulisAtomik(path string, data []byte) error {
dir := filepath.Dir(path)
base := filepath.Base(path)
tmp, err := os.CreateTemp(dir, base+".tmp-*")
if err != nil {
return err
}
sementara := tmp.Name()
if _, err := tmp.Write(data); err != nil {
tmp.Close()
os.Remove(sementara)
return err
}
// Sync memaksa data turun ke media penyimpanan sebelum rename.
if err := tmp.Sync(); err != nil {
tmp.Close()
os.Remove(sementara)
return err
}
if err := tmp.Close(); err != nil {
os.Remove(sementara)
return err
}
if err := os.Rename(sementara, path); err != nil {
os.Remove(sementara)
return err
}
return nil
}