BAB 42: Command-Line Arguments dan Flag
Pelajari cara membuat program Go yang menerima input dari command line menggunakan os.Args untuk argumen posisional dan package flag untuk parameter berstruktur.
Di bab sebelumnya, kita menulis program yang semua inputnya tertanam di dalam kode — nama file, string yang di-hash, konfigurasi akun. Pendekatan itu cukup untuk belajar, tapi tidak praktis untuk program yang dipakai orang lain atau dijalankan di lingkungan berbeda. Bayangkan harus recompile setiap kali ingin mengganti nama file yang diproses.
Program Go yang serius — skrip backup, tool validasi data, binary di server — menerima instruksi dari luar saat dijalankan. Go menyediakan dua mekanisme untuk ini: argumen posisional lewat os.Args, dan flag berstruktur lewat package flag.
Argumen Posisional dengan os.Args
os.Args adalah slice []string yang berisi semua yang diketikkan saat menjalankan program. Indeks 0 selalu berisi path ke executable, sehingga argumen yang sebenarnya dimulai dari indeks 1.
// jalankan.go
package main
import (
"fmt"
"os"
)
func main() {
semua := os.Args
args := semua[1:] // buang nama program di indeks 0
fmt.Printf("nama program : %s\n", semua[0])
fmt.Printf("jumlah args : %d\n", len(args))
for i, a := range args {
fmt.Printf("args[%d] = %q\n", i, a)
}
}
Jalankan dengan beberapa argumen:
go run jalankan.go laporan-ops 2026-03 verbose
nama program : /tmp/go-build.../exe/jalankan
jumlah args : 3
args[0] = "laporan-ops"
args[1] = "2026-03"
args[2] = "verbose"
Argumen yang mengandung spasi harus dibungkus tanda kutip agar dianggap satu nilai:
go run jalankan.go "laporan keuangan" 2026-03
os.Args cocok untuk program sederhana dengan urutan argumen yang tetap dan terdokumentasi. Tapi begitu argumen bertambah banyak atau urutannya bisa bervariasi, pendekatan ini cepat menjadi rapuh. Di situlah flag lebih unggul.
Flag Berstruktur dengan package flag
Flag menggunakan format --nama=nilai atau -nama nilai, memberikan setiap parameter nama yang eksplisit. Ini jauh lebih mudah dibaca dan lebih toleran terhadap urutan.
// laporan.go
package main
import (
"flag"
"fmt"
)
func main() {
divisi := flag.String("divisi", "semua", "nama divisi yang dilaporkan")
periode := flag.String("periode", "2026-01", "periode laporan (YYYY-MM)")
tampilDetail := flag.Bool("detail", false, "tampilkan detail per entri")
batasEntri := flag.Int("batas", 10, "jumlah maksimal entri ditampilkan")
flag.Parse()
fmt.Printf("divisi : %s\n", *divisi)
fmt.Printf("periode : %s\n", *periode)
fmt.Printf("detail : %v\n", *tampilDetail)
fmt.Printf("batas : %d\n", *batasEntri)
}
go run laporan.go -divisi=OPS -periode=2026-03 -detail -batas=25
divisi : OPS
periode : 2026-03
detail : true
batas : 25
Beberapa hal yang perlu diperhatikan:
flag.String(),flag.Bool(),flag.Int()mengembalikan pointer, bukan nilai langsung. Untuk membaca nilainya, gunakan operator dereference*divisi.flag.Parse()wajib dipanggil setelah semua flag didefinisikan. Tanpa ini, semua nilai flag akan tetap sebagai default.- Flag
booltidak membutuhkan=true— cukup tulis-detailuntuk mengaktifkannya.
package flag otomatis membuat teks bantuan. Jalankan program dengan -help atau --help untuk melihatnya. Teks yang kamu tulis sebagai argumen ketiga pada flag.String(...) akan muncul di sana.
Variabel Var vs Pointer
Selain mendapatkan pointer dari return value, kamu bisa mendeklarasikan variabel terlebih dahulu lalu menghubungkannya ke flag menggunakan fungsi *Var:
// laporan.go
package main
import (
"flag"
"fmt"
)
func main() {
// cara pointer (dari return value)
divisi := flag.String("divisi", "semua", "nama divisi")
// cara Var (variabel dideklarasikan dulu)
var periode string
var batasEntri int
flag.StringVar(&periode, "periode", "2026-01", "periode laporan")
flag.IntVar(&batasEntri, "batas", 10, "batas entri")
flag.Parse()
// cara pointer: butuh dereference
fmt.Println("divisi :", *divisi)
// cara Var: langsung pakai variabel
fmt.Println("periode :", periode)
fmt.Println("batas :", batasEntri)
}
Keduanya menghasilkan perilaku yang identik. Pilih gaya yang terasa lebih natural untuk konteks programmu — *Var lebih bersih saat variabel itu dipakai di banyak tempat, sedangkan pointer lebih ringkas untuk flag yang hanya dipakai sekali.
Tipe Flag yang Tersedia
package flag mendukung semua tipe primitif yang umum. Tabel berikut merangkum fungsi yang tersedia beserta pasangan *Var-nya:
| Fungsi | Fungsi Var | Tipe nilai |
|---|---|---|
flag.String() | flag.StringVar() | string |
flag.Bool() | flag.BoolVar() | bool |
flag.Int() | flag.IntVar() | int |
flag.Int64() | flag.Int64Var() | int64 |
flag.Float64() | flag.Float64Var() | float64 |
flag.Uint() | flag.UintVar() | uint |
flag.Duration() | flag.DurationVar() | time.Duration |
flag.Duration() sangat berguna untuk timeout dan interval — ia menerima string seperti "30s", "5m", atau "2h" dan langsung mengonversinya ke time.Duration.
Studi Kasus: Tool Ekspor Laporan
Menggabungkan argumen posisional dan flag dalam satu program yang berguna.
// laporan.go
package main
import (
"flag"
"fmt"
"os"
"strings"
)
func main() {
// flag opsional dengan nilai default yang masuk akal
format := flag.String("format", "teks", "format output: teks atau csv")
periode := flag.String("periode", "2026-01", "periode laporan (YYYY-MM)")
tampilHeader := flag.Bool("header", true, "tampilkan baris header")
flag.Parse()
// argumen posisional: nama divisi (wajib)
divisiList := flag.Args() // sisa argumen setelah flag di-parse
if len(divisiList) == 0 {
fmt.Fprintln(os.Stderr, "error: sebutkan minimal satu nama divisi")
fmt.Fprintln(os.Stderr, "contoh: go run laporan.go -periode=2026-03 OPS FIN IT")
os.Exit(1)
}
// validasi format
*format = strings.ToLower(*format)
if *format != "teks" && *format != "csv" {
fmt.Fprintf(os.Stderr, "error: format %q tidak dikenal (gunakan teks atau csv)\n", *format)
os.Exit(1)
}
// tampilkan konfigurasi yang akan dijalankan
fmt.Printf("ekspor laporan\n")
fmt.Printf(" periode : %s\n", *periode)
fmt.Printf(" format : %s\n", *format)
fmt.Printf(" header : %v\n", *tampilHeader)
fmt.Printf(" divisi : %s\n", strings.Join(divisiList, ", "))
fmt.Println()
// simulasi output
if *tampilHeader {
if *format == "csv" {
fmt.Println("divisi,periode,total_transaksi,nilai")
} else {
fmt.Printf("%-12s %-10s %18s %15s\n", "DIVISI", "PERIODE", "TOTAL TRANSAKSI", "NILAI")
fmt.Println(strings.Repeat("-", 58))
}
}
for _, d := range divisiList {
if *format == "csv" {
fmt.Printf("%s,%s,142,987500000\n", strings.ToUpper(d), *periode)
} else {
fmt.Printf("%-12s %-10s %18d %15d\n", strings.ToUpper(d), *periode, 142, 987500000)
}
}
}
go run laporan.go -periode=2026-03 -format=teks OPS FIN IT
ekspor laporan
periode : 2026-03
format : teks
header : true
divisi : OPS, FIN, IT
DIVISI PERIODE TOTAL TRANSAKSI NILAI
----------------------------------------------------------
OPS 2026-03 142 987500000
FIN 2026-03 142 987500000
IT 2026-03 142 987500000
flag.Args() mengembalikan slice argumen yang tersisa setelah semua flag dikonsumsi — ini cara idiomatis Go untuk menggabungkan flag terstruktur dengan argumen posisional.
Selalu validasi input dari command line dan berikan pesan error yang informatif ke os.Stderr. Pesan error harus menyertakan contoh penggunaan yang benar — pengguna yang membaca error tidak harus membuka dokumentasi terpisah untuk tahu cara memperbaikinya.
Latihan
Latihan 1 — Tool hash CLI:
Buat program yang menerima flag -algoritma (nilai: sha1 atau sha256, default sha1) dan satu atau lebih argumen posisional berupa string yang akan di-hash. Program mencetak setiap string beserta hash-nya. Gabungkan dengan pengetahuan dari Bab 41.
Latihan 2 — Konfigurasi dari flag:
Buat program yang menerima flag -host (default localhost), -port (default 8080), dan -debug (bool, default false). Program mencetak konfigurasi yang akan digunakan dan memberi peringatan ke os.Stderr jika port di bawah 1024.
Latihan 3 — Subcommand sederhana:
Buat program yang menerima argumen posisional pertama sebagai “perintah” (encode atau decode), dan argumen kedua sebagai datanya. Gunakan flag.Args() setelah flag.Parse() untuk mengambil keduanya. Untuk encode, tampilkan Base64 dari data; untuk decode, tampilkan string asli dari input Base64. Gabungkan dengan pengetahuan dari Bab 40.
Sekarang program Go yang kamu tulis bisa menerima instruksi dari luar — fleksibel dijalankan dengan konfigurasi berbeda tanpa perlu mengubah kode. Ini adalah fondasi dari semua tool CLI: dari skrip sederhana hingga binary kompleks seperti kubectl atau docker.
Tapi menerima input dari luar baru setengah cerita. Tool yang berguna sering juga perlu memanggil program lain — menjalankan git, mengeksekusi skrip shell, atau memicu command sistem. Di bab berikutnya kita akan belajar cara melakukan itu: menjalankan perintah command line dari dalam program Go menggunakan os/exec.