Flag #
Hampir setiap program Go yang berjalan dari command line membutuhkan cara untuk menerima konfigurasi dari pengguna — apakah port yang digunakan, file konfigurasi yang dibaca, atau mode debug yang diaktifkan. Package flag menyediakan parsing argumen command line yang sederhana dan idiomatis: definisikan flag-mu, panggil flag.Parse(), dan nilai sudah tersedia sebagai pointer atau variabel. Package ini sengaja minimalis — tidak mendukung subcommand bertingkat atau format --flag (hanya -flag dan --flag dengan satu sama dua dash), tapi untuk kebutuhan tooling sederhana hingga menengah, ia lebih dari cukup. Untuk CLI yang lebih kompleks, FlagSet memungkinkan implementasi subcommand, dan kombinasi dengan environment variable membuat konfigurasi yang fleksibel.
Gambaran Besar Package flag #
flowchart TD
F["package flag"] --> Define["Mendefinisikan Flag"]
F --> Parse["Parsing"]
F --> Access["Mengakses Nilai"]
F --> Custom["Custom Type"]
F --> FlagSet["FlagSet\nuntuk subcommand"]
Define --> D1["flag.String(name, default, usage)\nreturn *string"]
Define --> D2["flag.Int(name, default, usage)\nreturn *int"]
Define --> D3["flag.Bool(name, default, usage)\nreturn *bool"]
Define --> D4["flag.Duration(name, default, usage)\nreturn *time.Duration"]
Define --> D5["flag.StringVar(&var, name, default, usage)\nlangsung ke variabel"]
Parse --> P1["flag.Parse()\nparse os.Args[1:]"]
Parse --> P2["flag.Args()\nargumen non-flag setelah parse"]
Parse --> P3["flag.NArg()\njumlah argumen non-flag"]
Access --> A1["*namaFlag — dereference pointer"]
Access --> A2["flag.Lookup(name)\ncari flag yang sudah didefinisikan"]
FlagSet --> FS1["flag.NewFlagSet(name, errorHandling)"]
FlagSet --> FS2["fs.Parse(args)\nparse slice argumen tertentu"]
style F fill:#4f86c6,color:#fff
style Define fill:#e8f5e9
style Parse fill:#e3f2fd
style Access fill:#fff3e0
style FlagSet fill:#f3e5f5Flag Dasar #
package main
import (
"flag"
"fmt"
"os"
)
func main() {
// Definisikan flag — kembalikan pointer ke nilai
host := flag.String("host", "localhost", "host server")
port := flag.Int("port", 8080, "port server")
debug := flag.Bool("debug", false, "aktifkan mode debug")
timeout := flag.Duration("timeout", 30*time.Second, "timeout koneksi")
config := flag.String("config", "", "path ke file konfigurasi")
// Alternatif: langsung ke variabel yang sudah ada
var verbose bool
flag.BoolVar(&verbose, "verbose", false, "output verbose")
var maxConn int
flag.IntVar(&maxConn, "max-conn", 10, "jumlah koneksi maksimal")
// Parse argumen dari os.Args[1:]
// HARUS dipanggil setelah semua flag didefinisikan, sebelum digunakan
flag.Parse()
// Akses nilai dengan dereference pointer
fmt.Printf("Host: %s\n", *host)
fmt.Printf("Port: %d\n", *port)
fmt.Printf("Debug: %v\n", *debug)
fmt.Printf("Timeout: %v\n", *timeout)
fmt.Printf("Verbose: %v\n", verbose)
fmt.Printf("MaxConn: %d\n", maxConn)
// Argumen non-flag (setelah semua flag)
// Contoh: ./app -port 9090 file1.txt file2.txt
args := flag.Args() // ["file1.txt", "file2.txt"]
fmt.Printf("Argumen: %v\n", args)
fmt.Printf("Jumlah argumen: %d\n", flag.NArg())
}
Format Flag yang Didukung #
# Format yang valid — semuanya setara
./app -host localhost
./app --host localhost
./app -host=localhost
./app --host=localhost
# Boolean flag — keduanya setara
./app -debug
./app -debug=true
./app -debug=false
# Urutan tidak penting untuk flag
./app -port 9090 -host example.com -debug
# Argumen non-flag di akhir
./app -port 9090 file1.txt file2.txt
# flag.Args() = ["file1.txt", "file2.txt"]
# -- menghentikan parsing flag
./app -port 9090 -- -ini-bukan-flag.txt
# flag.Args() = ["-ini-bukan-flag.txt"]
Usage — Pesan Bantuan #
func main() {
// Kustomisasi pesan usage
flag.Usage = func() {
fmt.Fprintf(os.Stderr, "Penggunaan: %s [flags] [file...]\n\n", os.Args[0])
fmt.Fprintf(os.Stderr, "Server HTTP sederhana.\n\n")
fmt.Fprintf(os.Stderr, "Flag:\n")
flag.PrintDefaults() // cetak semua flag dengan format standar
fmt.Fprintf(os.Stderr, "\nContoh:\n")
fmt.Fprintf(os.Stderr, " %s -port 9090 -debug\n", os.Args[0])
fmt.Fprintf(os.Stderr, " %s -config /etc/myapp/config.yaml\n", os.Args[0])
}
host := flag.String("host", "0.0.0.0", "alamat `host` yang di-listen")
port := flag.Int("port", 8080, "nomor `port` (1-65535)")
flag.Parse()
_ = host
_ = port
}
// Output dari -help atau -h:
// Penggunaan: ./myapp [flags] [file...]
//
// Server HTTP sederhana.
//
// Flag:
// -host host
// alamat host yang di-listen (default "0.0.0.0")
// -port port
// nomor port (1-65535) (default 8080)
//
// Contoh:
// ./myapp -port 9090 -debug
// ./myapp -config /etc/myapp/config.yaml
Custom Flag Type #
Untuk tipe yang tidak didukung secara bawaan (selain string, int, bool, float64, duration), implementasikan interface flag.Value:
// flag.Value interface:
// type Value interface {
// String() string — nilai default dan tampilan saat -help
// Set(string) error — set nilai dari string argumen
// }
// Contoh 1: flag untuk slice string (nilai berulang)
type StringSlice []string
func (ss *StringSlice) String() string {
return strings.Join(*ss, ", ")
}
func (ss *StringSlice) Set(s string) error {
*ss = append(*ss, s)
return nil
}
// Penggunaan:
// ./app -tag backend -tag api -tag v2
// tags akan berisi ["backend", "api", "v2"]
// Contoh 2: flag untuk level log
type LogLevel int
const (
LevelDebug LogLevel = iota
LevelInfo
LevelWarn
LevelError
)
func (l *LogLevel) String() string {
switch *l {
case LevelDebug:
return "debug"
case LevelInfo:
return "info"
case LevelWarn:
return "warn"
case LevelError:
return "error"
default:
return "unknown"
}
}
func (l *LogLevel) Set(s string) error {
switch strings.ToLower(s) {
case "debug":
*l = LevelDebug
case "info":
*l = LevelInfo
case "warn", "warning":
*l = LevelWarn
case "error":
*l = LevelError
default:
return fmt.Errorf("level tidak valid: %q (valid: debug, info, warn, error)", s)
}
return nil
}
// Mendaftarkan custom flag
func main() {
var tags StringSlice
flag.Var(&tags, "tag", "tag untuk filter (bisa diulang)")
var logLevel LogLevel = LevelInfo // default Info
flag.Var(&logLevel, "log-level", "level logging (debug|info|warn|error)")
flag.Parse()
fmt.Println("Tags:", tags)
fmt.Println("Log level:", logLevel)
}
FlagSet — Subcommand #
flag.FlagSet memungkinkan definisi flag per subcommand — setiap subcommand punya set flag-nya sendiri yang terpisah:
flowchart TD
App["./myapp"] --> Args["os.Args"]
Args --> Sub{subcommand?}
Sub -- "serve" --> ServFS["serveFlagSet\n -port int\n -host string\n -tls bool"]
Sub -- "migrate" --> MigrFS["migrateFlagSet\n -db string\n -dry-run bool\n -steps int"]
Sub -- "export" --> ExpFS["exportFlagSet\n -format string\n -output string\n -compress bool"]
Sub -- "help / lainnya" --> Help["Tampilkan usage"]
ServFS --> ServeCmd["jalankanServer()"]
MigrFS --> MigrCmd["jalankanMigrasi()"]
ExpFS --> ExpCmd["jalankanEkspor()"]
style App fill:#4f86c6,color:#fff
style ServFS fill:#e8f5e9
style MigrFS fill:#e3f2fd
style ExpFS fill:#fff3e0func main() {
// Periksa subcommand
if len(os.Args) < 2 {
fmt.Fprintf(os.Stderr, "Penggunaan: %s <subcommand> [flags]\n\n", os.Args[0])
fmt.Fprintf(os.Stderr, "Subcommand yang tersedia:\n")
fmt.Fprintf(os.Stderr, " serve — jalankan HTTP server\n")
fmt.Fprintf(os.Stderr, " migrate — jalankan migrasi database\n")
fmt.Fprintf(os.Stderr, " export — ekspor data\n")
os.Exit(1)
}
switch os.Args[1] {
case "serve":
jalankanServe(os.Args[2:])
case "migrate":
jalankanMigrasi(os.Args[2:])
case "export":
jalankanEkspor(os.Args[2:])
case "help", "-help", "--help", "-h":
// Tampilkan help umum
fmt.Println("Gunakan: myapp <subcommand> -help untuk bantuan")
default:
fmt.Fprintf(os.Stderr, "Subcommand tidak dikenal: %q\n", os.Args[1])
os.Exit(1)
}
}
func jalankanServe(args []string) {
// FlagSet untuk subcommand serve
fs := flag.NewFlagSet("serve", flag.ExitOnError)
host := fs.String("host", "0.0.0.0", "alamat host")
port := fs.Int("port", 8080, "nomor port")
tls := fs.Bool("tls", false, "aktifkan TLS")
certFile := fs.String("cert", "", "path ke sertifikat TLS")
keyFile := fs.String("key", "", "path ke private key TLS")
// Kustomisasi usage untuk subcommand ini
fs.Usage = func() {
fmt.Fprintf(os.Stderr, "Penggunaan: myapp serve [flags]\n\nFlag:\n")
fs.PrintDefaults()
}
// Parse args untuk subcommand ini saja
if err := fs.Parse(args); err != nil {
os.Exit(1)
}
// Validasi
if *tls && (*certFile == "" || *keyFile == "") {
fmt.Fprintf(os.Stderr, "Error: -cert dan -key wajib jika -tls diaktifkan\n")
os.Exit(1)
}
fmt.Printf("Menjalankan server di %s:%d (TLS: %v)\n", *host, *port, *tls)
}
func jalankanMigrasi(args []string) {
fs := flag.NewFlagSet("migrate", flag.ExitOnError)
dsn := fs.String("db", os.Getenv("DATABASE_URL"), "DSN database")
dryRun := fs.Bool("dry-run", false, "tampilkan SQL tanpa eksekusi")
steps := fs.Int("steps", 0, "jumlah migrasi (0 = semua)")
fs.Usage = func() {
fmt.Fprintf(os.Stderr, "Penggunaan: myapp migrate [flags]\n\nFlag:\n")
fs.PrintDefaults()
}
fs.Parse(args)
if *dsn == "" {
fmt.Fprintf(os.Stderr, "Error: -db atau DATABASE_URL wajib diisi\n")
os.Exit(1)
}
fmt.Printf("Migrasi database: %s (dry-run: %v, steps: %d)\n",
*dsn, *dryRun, *steps)
}
func jalankanEkspor(args []string) {
fs := flag.NewFlagSet("export", flag.ExitOnError)
format := fs.String("format", "json", "format output (json|csv|xlsx)")
output := fs.String("output", "-", "file output (- untuk stdout)")
compress := fs.Bool("compress", false, "kompres output dengan gzip")
fs.Parse(args)
fmt.Printf("Ekspor: format=%s, output=%s, compress=%v\n",
*format, *output, *compress)
}
Integrasi dengan Environment Variable #
Pattern yang sangat umum di aplikasi produksi adalah mendukung konfigurasi dari keduanya — flag CLI untuk penggunaan interaktif, environment variable untuk deployment otomatis:
flowchart LR
subgraph Prioritas["Prioritas Konfigurasi (tinggi ke rendah)"]
P1["1. Flag CLI\n(-port 9090)"]
P2["2. Environment Variable\n(PORT=9090)"]
P3["3. File Konfigurasi\n(config.yaml)"]
P4["4. Default Value\n(port: 8080)"]
end
P1 --> P2 --> P3 --> P4
style P1 fill:#e8f5e9
style P2 fill:#e3f2fd
style P3 fill:#fff3e0
style P4 fill:#f3e5f5// Helper: ambil dari env atau gunakan default
func envOr(key, defaultVal string) string {
if val := os.Getenv(key); val != "" {
return val
}
return defaultVal
}
func envOrInt(key string, defaultVal int) int {
if val := os.Getenv(key); val != "" {
n, err := strconv.Atoi(val)
if err == nil {
return n
}
}
return defaultVal
}
func envOrBool(key string, defaultVal bool) bool {
if val := os.Getenv(key); val != "" {
b, err := strconv.ParseBool(val)
if err == nil {
return b
}
}
return defaultVal
}
// Konfigurasi yang mendukung keduanya
func main() {
// Default diambil dari env, tapi bisa di-override dengan flag
host := flag.String("host",
envOr("APP_HOST", "0.0.0.0"),
"host server (env: APP_HOST)")
port := flag.Int("port",
envOrInt("PORT", 8080),
"port server (env: PORT)")
debug := flag.Bool("debug",
envOrBool("APP_DEBUG", false),
"mode debug (env: APP_DEBUG)")
dbURL := flag.String("db",
envOr("DATABASE_URL", ""),
"URL database (env: DATABASE_URL)")
flag.Parse()
// Validasi nilai wajib
if *dbURL == "" {
fmt.Fprintf(os.Stderr,
"Error: -db atau DATABASE_URL wajib diisi\n")
flag.Usage()
os.Exit(1)
}
fmt.Printf("Konfigurasi:\n")
fmt.Printf(" Host: %s\n", *host)
fmt.Printf(" Port: %d\n", *port)
fmt.Printf(" Debug: %v\n", *debug)
fmt.Printf(" DB: %s\n", maskDSN(*dbURL))
}
func maskDSN(dsn string) string {
// Sembunyikan password dari DSN untuk logging
re := regexp.MustCompile(`://[^:]+:[^@]+@`)
return re.ReplaceAllString(dsn, "://***:***@")
}
ErrorHandling di FlagSet #
// flag.ContinueOnError — kembalikan error, jangan panic/exit
fs := flag.NewFlagSet("myapp", flag.ContinueOnError)
// flag.ExitOnError — panggil os.Exit(2) saat error (default untuk flag package)
fs2 := flag.NewFlagSet("myapp", flag.ExitOnError)
// flag.PanicOnError — panic saat error
fs3 := flag.NewFlagSet("myapp", flag.PanicOnError)
// Contoh penggunaan ContinueOnError untuk kontrol penuh
fs4 := flag.NewFlagSet("myapp", flag.ContinueOnError)
var buf bytes.Buffer
fs4.SetOutput(&buf) // redirect output error ke buffer
port := fs4.Int("port", 8080, "port server")
if err := fs4.Parse(os.Args[1:]); err != nil {
if err == flag.ErrHelp {
// Pengguna meminta -help
fmt.Println(buf.String())
os.Exit(0)
}
fmt.Fprintf(os.Stderr, "Error parsing flags: %v\n%s", err, buf.String())
os.Exit(1)
}
_ = port
Pola Penggunaan di Produksi #
Config Struct dari Flag #
// Kumpulkan semua konfigurasi dalam satu struct
type AppConfig struct {
Host string
Port int
Debug bool
DatabaseURL string
LogLevel string
MaxWorkers int
Timeout time.Duration
Tags []string
}
func parseConfig() *AppConfig {
cfg := &AppConfig{}
// Flag dengan nilai default dari environment
flag.StringVar(&cfg.Host, "host",
envOr("HOST", "0.0.0.0"), "alamat host (env: HOST)")
flag.IntVar(&cfg.Port, "port",
envOrInt("PORT", 8080), "port server (env: PORT)")
flag.BoolVar(&cfg.Debug, "debug",
envOrBool("DEBUG", false), "mode debug (env: DEBUG)")
flag.StringVar(&cfg.DatabaseURL, "db",
envOr("DATABASE_URL", ""), "URL database (env: DATABASE_URL)")
flag.StringVar(&cfg.LogLevel, "log-level",
envOr("LOG_LEVEL", "info"), "level log (env: LOG_LEVEL)")
flag.IntVar(&cfg.MaxWorkers, "workers",
envOrInt("MAX_WORKERS", 4), "jumlah worker (env: MAX_WORKERS)")
flag.DurationVar(&cfg.Timeout, "timeout",
30*time.Second, "timeout operasi")
// Custom flag untuk slice
var tags StringSlice
flag.Var(&tags, "tag", "tag filter (bisa diulang)")
flag.Parse()
cfg.Tags = []string(tags)
return cfg
}
func validasiConfig(cfg *AppConfig) error {
var errs []string
if cfg.DatabaseURL == "" {
errs = append(errs, "database URL wajib diisi (-db atau DATABASE_URL)")
}
if cfg.Port < 1 || cfg.Port > 65535 {
errs = append(errs, fmt.Sprintf("port harus 1-65535, dapat: %d", cfg.Port))
}
validLevels := map[string]bool{"debug": true, "info": true, "warn": true, "error": true}
if !validLevels[cfg.LogLevel] {
errs = append(errs, fmt.Sprintf("log-level tidak valid: %q", cfg.LogLevel))
}
if len(errs) > 0 {
return fmt.Errorf("konfigurasi tidak valid:\n - %s",
strings.Join(errs, "\n - "))
}
return nil
}
func main() {
cfg := parseConfig()
if err := validasiConfig(cfg); err != nil {
fmt.Fprintln(os.Stderr, err)
fmt.Fprintf(os.Stderr, "\nGunakan -help untuk melihat semua flag yang tersedia.\n")
os.Exit(1)
}
fmt.Printf("Menjalankan aplikasi dengan konfigurasi:\n")
fmt.Printf(" Host: %s:%d\n", cfg.Host, cfg.Port)
fmt.Printf(" Debug: %v\n", cfg.Debug)
fmt.Printf(" Workers: %d\n", cfg.MaxWorkers)
fmt.Printf(" Timeout: %v\n", cfg.Timeout)
fmt.Printf(" Log: %s\n", cfg.LogLevel)
}
CLI Tool Lengkap #
// Tool untuk manipulasi file — contoh CLI yang lengkap
func main() {
// Global flag
verbose := flag.Bool("v", false, "output verbose")
version := flag.Bool("version", false, "tampilkan versi")
flag.Usage = func() {
fmt.Fprintf(os.Stderr, `Penggunaan: filetool [flags] <subcommand> [subcommand-flags] [args]
Subcommand:
copy — salin file
move — pindahkan file
hash — hitung hash file
search — cari file
Flag global:
`)
flag.PrintDefaults()
fmt.Fprintf(os.Stderr, "\nGunakan 'filetool <subcommand> -help' untuk bantuan subcommand.\n")
}
// Parsing dengan -v dan -version harus sebelum subcommand
// Tapi stop sebelum subcommand untuk tidak memblokir flag-nya
flag.Parse()
if *version {
fmt.Println("filetool v1.0.0")
os.Exit(0)
}
args := flag.Args()
if len(args) == 0 {
flag.Usage()
os.Exit(1)
}
subcommand := args[0]
subArgs := args[1:]
switch subcommand {
case "copy":
subCopy(*verbose, subArgs)
case "move":
subMove(*verbose, subArgs)
case "hash":
subHash(*verbose, subArgs)
case "search":
subSearch(*verbose, subArgs)
default:
fmt.Fprintf(os.Stderr, "Error: subcommand tidak dikenal: %q\n\n", subcommand)
flag.Usage()
os.Exit(1)
}
}
func subHash(verbose bool, args []string) {
fs := flag.NewFlagSet("hash", flag.ExitOnError)
algo := fs.String("algo", "sha256", "algoritma hash (sha256|sha512|md5)")
fs.Usage = func() {
fmt.Fprintf(os.Stderr, "Penggunaan: filetool hash [flags] <file...>\n\nFlag:\n")
fs.PrintDefaults()
}
fs.Parse(args)
files := fs.Args()
if len(files) == 0 {
fmt.Fprintln(os.Stderr, "Error: minimal satu file diperlukan")
fs.Usage()
os.Exit(1)
}
for _, f := range files {
hash, err := hitungHashFile(f, *algo)
if err != nil {
fmt.Fprintf(os.Stderr, "Error: %v\n", err)
continue
}
if verbose {
fmt.Printf("%s (%s): %s\n", f, *algo, hash)
} else {
fmt.Printf("%s %s\n", hash, f)
}
}
}
Kapan Beralih ke Alternatif #
Tetap gunakan flag jika:
✓ Tools sederhana dengan sedikit flag
✓ Zero external dependency
✓ Flag tanpa subcommand bertingkat
✓ Ingin sesuatu yang idiomatis dan mudah dipahami
Pertimbangkan library eksternal jika:
✗ Subcommand bertingkat (app server start --port 8080)
→ cobra (github.com/spf13/cobra) — paling populer
→ urfave/cli — alternatif yang lebih sederhana
✗ Auto-generate bash/zsh completion
→ cobra mendukung ini built-in
✗ Validasi dan transformasi flag yang kompleks
→ kong (github.com/alecthomas/kong)
✗ Konfigurasi dari file + env + flag dalam satu package
→ viper (github.com/spf13/viper) — sering dipakai dengan cobra
✗ POSIX-style flag dengan shorthand (-v, --verbose)
→ pflag (github.com/spf13/pflag) — dipakai oleh cobra
Catatan: flag package mendukung baik -flag maupun --flag,
tapi tidak mendukung penggabungan shorthand seperti -vdf (= -v -d -f)
Ringkasan #
flag.Parse()harus dipanggil setelah semua flag didefinisikan dan sebelum nilai flag diakses — biasanya di awalmain().flag.StringVar,flag.IntVar, dll. untuk mengisi langsung ke variabel yang sudah ada — lebih bersih dari menyimpan pointer hasilflag.String()dll.flag.Usagebisa di-override dengan fungsi custom untuk pesan bantuan yang lebih informatif dan branded.flag.FlagSetuntuk subcommand — setiap subcommand punya set flag-nya sendiri yang di-parse darios.Args[2:](atau slice yang relevan).- Kombinasikan dengan environment variable menggunakan pola
envOr— flag CLI bisa override env, tapi env menyediakan default yang lebih fleksibel dari hardcoded value.- Custom
flag.Valueuntuk tipe yang tidak didukung bawaan — implementasikanString()danSet(string) erroruntuk tipe apapun seperti slice, enum, atau custom struct.- Validasi setelah
flag.Parse()— periksa nilai flag yang diparsing untuk memastikan kombinasi flag yang valid (misalnya:-tlstanpa-certdan-key).flag.Args()untuk mengakses argumen non-flag setelah parsing — berguna untuk posisional arguments seperti nama file.flag.ErrHelpsaat menggunakanContinueOnError— deteksi permintaan-helpdan tangani secara eksplisit untuk kontrol penuh atas output.