Lompati ke konten utama
Modul 10 · Pelajaran 4 dari 549/50 dalam kursus~18 min
Pelajaran modul (4/5)

Proyek mini: CLI dengan flag

Mari kita satukan blok penyusun modul sebelumnya untuk membuat CLI idiomatik kecil: penguraian flag dengan paket flag, penanganan argumen posisi, kesalahan pada os.Stderr, dan kode keluar yang benar.

Paket flag

Go
package main

import (
    "flag"
    "fmt"
    "os"
)

func main() {
    n := flag.Int("n", 1, "number of repetitions")
    upper := flag.Bool("u", false, "print in uppercase")
    flag.Parse()

    args := flag.Args() // remaining positional arguments
    if len(args) < 1 {
        fmt.Fprintln(os.Stderr, "usage: echo [-n N] [-u] <text>")
        os.Exit(2)
    }

    text := args[0]
    if *upper {
        text = strings.ToUpper(text)
    }
    for i := 0; i < *n; i++ {
        fmt.Println(text)
    }
}

Poin-poin penting:

  • flag.Int, flag.String, flag.Bool, flag.Duration (dll.) mengembalikan pointer: Anda menggunakan *n, *upper.
  • flag.Parse() harus dipanggil SEKALI, sebelum membaca nilainya dan sebelum flag.Args().
  • flag.Args() mengembalikan argumen posisional (yang setelah -- atau setelah tanda terakhir).
  • Formulir yang diterima: -n 3, -n=3, --n=3. Boolean: -u (benar), -u=false.

Kode keluar konvensional

Go
os.Exit(0) // success
os.Exit(1) // generic error
os.Exit(2) // usage error (incorrect flag usage)

Shell dan skrip mengharapkan kode-kode ini: gunakanlah secara konsisten. Jangan pernah keluar dari goroutine non-utama dengan os.Exit: ia akan melewatkan setiap defer, termasuk pembilasan log.

Stderr vs Stdout

Aturan Unix yang lama dan sakral:

  • os.Stdout → keluaran berguna, dapat disalurkan (cli | grep ...).
  • os.Stderr → diagnostik, kesalahan, bilah kemajuan, penggunaan.
Go
fmt.Println("result: 42")                   // stdout
fmt.Fprintln(os.Stderr, "error: ...")       // stderr

Dengan cara ini cli 2>/dev/null hanya menampilkan hasil, cli >out.txt hanya mengalihkan hasil dan kesalahan tetap terlihat.

log vs fmt

  • fmt untuk keluaran pengguna.
  • log untuk diagnostik: menambahkan stempel waktu, menulis ke os.Stderr secara default, memiliki log.Fatal (log + os.Exit(1)) dan log.Panic.
Go
log.SetFlags(log.LstdFlags | log.Lshortfile) // timestamp + file:line
log.Fatal("boom") // prints and exits with 1

Untuk aplikasi modern (Go 1.21+) gunakan log/slog untuk logging terstruktur (JSON atau teks, level, kolom yang diketik).

Arsitektur: pisahkan main dari logika

Go
func main() {
    if err := run(os.Args[1:], os.Stdout, os.Stderr); err != nil {
        fmt.Fprintln(os.Stderr, err)
        os.Exit(1)
    }
}

func run(args []string, stdout, stderr io.Writer) error {
    // here use a new flag.FlagSet instead of the global flag.CommandLine
    fs := flag.NewFlagSet("echo", flag.ContinueOnError)
    fs.SetOutput(stderr)
    n := fs.Int("n", 1, "repetitions")
    if err := fs.Parse(args); err != nil {
        return err
    }
    // ...
    return nil
}

Keuntungan:

  • Dapat diuji: Anda meneruskan bytes.Buffer sebagai stdout/stderr dan memverifikasi hasilnya.
  • Tidak ada status global: setiap FlagSet diisolasi.
  • main tetap kecil: mempersiapkan delegasi IO+.

Sub-perintah

Untuk CLI dengan subperintah (mycli serve, mycli migrate), skema tanpa ketergantungan eksternal:

Go
func main() {
    if len(os.Args) < 2 {
        usage()
        os.Exit(2)
    }
    switch os.Args[1] {
    case "serve":
        serveCmd(os.Args[2:])
    case "migrate":
        migrateCmd(os.Args[2:])
    default:
        usage()
        os.Exit(2)
    }
}

Setiap subperintah memiliki flag.NewFlagSet sendiri. Untuk proyek yang lebih besar, perpustakaan seperti cobra atau urfave/cli menyusun skema ini dengan bantuan yang dibuat secara otomatis.

Latihan

Latihan#go.m10.l4.e1
Upaya: 0Memuat…

Definisikan flag -n jenis ini dengan nilai default 1 dan deskripsi 'ripetizioni', sehingga flag.Parse() dan stempel nilainya.

Memuat editor…

Solusi tersedia setelah 3 upaya

Latihan#go.m10.l4.e2
Upaya: 0Memuat…

Jika tidak ada posisi argumen yang sama dengan flag. Parse, cap 'manca argomento' pada Stderr dan akhiri dengan os.Exit(1).

Memuat editor…

Solusi tersedia setelah 3 upaya

Quiz#go.m10.l4.e3
Siap

Apakah aliran ini menampilkan pesan kesalahan dan penggunaan di CLI Unix-friendly?

Go
fmt.Fprintln(???, "uso: prog ...")
Pilihan jawaban