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 bool tidak membutuhkan =true — cukup tulis -detail untuk 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:

FungsiFungsi VarTipe 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.

Referensi

  1. 1Package flag — Go Standard Library Documentation
  2. 2os.Args — Go Standard Library Documentation
  3. 3Command-Line Flags — Go by Example