Mert Tosun
← Yazılar
Go ile CLI Geliştirme: Komut Satırı Araçlarına Derinlemesine Rehber

Go ile CLI Geliştirme: Komut Satırı Araçlarına Derinlemesine Rehber

Mert TosunGo

Komut satırı araçları, geliştirici iş akışının omurgasıdır. Script’ler büyüdükçe bakımı zorlaşır; Go ile yazılmış bir CLI ise tek bir statik binary olarak dağıtılır, tip güvenliği sunar ve eşzamanlılık ihtiyacında bile sade kalır.

Bu yazıda, standart kütüphaneden flag paketine, yaygın ekosistem seçimi Cobra’ya ve dağıtıma kadar uçtan uca bir yol haritası çıkarıyoruz.

Neden Go ile CLI?

Beklenti Go’daki karşılık
Tek dosya ile dağıtım go build → platforma özel binary
Hızlı başlangıç Küçük binary, düşük bellek
Eşzamanlı işler Goroutine + kanallar
Zengin ekosistem Cobra, Viper, urfave/cli ve daha fazlası

Özellikle DevOps, veri dönüştürme ve iç araçlar (internal tooling) için Go CLI çok yaygın: kubectl, docker, hugo, terraform ekosisteminde Go’nun izleri var.


1. Proje iskeleti

mkdir mycli && cd mycli
go mod init github.com/kullanici/mycli

Minimum giriş noktası:

package main

import (
    "fmt"
    "os"
)

func main() {
    if len(os.Args) < 2 {
        fmt.Fprintln(os.Stderr, "kullanım: mycli <komut>")
        os.Exit(2)
    }
    fmt.Println("argümanlar:", os.Args[1:])
}

os.Args[0] program adıdır; geri kalan kullanıcı argümanlarıdır. Basit araçlar için yeterli olsa da, gerçek dünyada flag veya Cobra ile ölçeklemek daha sürdürülebilir.


2. flag paketi: parametreler ve varsayılanlar

Standart kütüphane, çoğu senaryo için yeterlidir:

package main

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

func main() {
    verbose := flag.Bool("v", false, "ayrıntılı çıktı")
    outPath := flag.String("o", "", "çıktı dosyası (boşsa stdout)")
    flag.Parse()

    if *verbose {
        fmt.Fprintln(os.Stderr, "verbose açık")
    }
    if *outPath != "" {
        fmt.Println("çıktı:", *outPath)
    }
    fmt.Println("konumsal argümanlar:", flag.Args())
}

Dikkat: flag.Parse() çağrılmadan flag’ler işlenmez. Konumsal argümanlar flag.Args() ile alınır.

Çıkış kodu için os.Exit yerine main’den dönmek test edilebilirliği artırır; üretim main’inde ise net kodlar kullanın (aşağıda).


3. Cobra ile alt komutlar

Birden fazla komut (mycli init, mycli deploy) için Cobra pratikte standarttır.

go get github.com/spf13/cobra@latest

Örnek yapı:

cmd/
  root.go       // kök komut, kalıcı flag’ler
  version.go    // mycli version
  run.go        // mycli run
main.go

root.go içinde tipik kalıp:

package cmd

import (
    "github.com/spf13/cobra"
)

var rootCmd = &cobra.Command{
    Use:   "mycli",
    Short: "Örnek CLI",
    Long:  `Uzun açıklama burada — yardım çıktısında görünür.`,
}

func Execute() error {
    return rootCmd.Execute()
}

Alt komut eklemek:

var runCmd = &cobra.Command{
    Use:   "run",
    Short: "işi çalıştırır",
    RunE: func(cmd *cobra.Command, args []string) error {
        // iş mantığı; hata dönerseniz Cobra exit code ayarlar
        return nil
    },
}

func init() {
    rootCmd.AddCommand(runCmd)
    runCmd.Flags().StringP("env", "e", "dev", "ortam adı")
}

Run yerine RunE kullanmak, hataları üst katmana iletmeyi kolaylaştırır ve testlerde mock’lamayı sadeleştirir.


4. stdin, stdout ve stderr

CLI sözleşmesi:

  • stdout — makine tarafından tüketilecek veri (pipe zinciri)
  • stderr — insan için log ve uyarılar
  • stdin — pipe veya yönlendirilmiş girdi
import (
    "io"
    "os"
)

func copyOut(r io.Reader) {
    _, _ = io.Copy(os.Stdout, r)
}

func logErr(msg string) {
    _, _ = fmt.Fprintln(os.Stderr, msg)
}

TTY olup olmadığını kontrol etmek (renk aç/kapa) için golang.org/x/term veya isatty tarzı küçük yardımcılar kullanılabilir; CI ortamında renkleri kapatmak kullanıcı deneyimini iyileştirir.


5. Çıkış kodları (exit codes)

Unix geleneğinde:

Kod Anlam
0 Başarı
1 Genel hata
2 Yanlış kullanım (misuse)

Cobra, RunE dönüş hatalarına göre genelde 1 döner. Özel kodlar gerekiyorsa:

import sysexit "github.com/cockroachdb/errors/withstack" // örnek değil — doğrudan:

import "os"

// ...
os.Exit(5) // dikkatli kullanın; testlerde main’i ayırmak daha iyidir

İyi pratik: İş mantığını Run dışında saf fonksiyonlarda tutun; main veya cmd katmanı sadece os.Exit ile sonuçları eşlesin. Böylece go test içinde gerçek çıkış kodu olmadan davranışı doğrularsınız.


6. Yapılandırma: bayrak + ortam + dosya

Sık kullanılan kombinasyon:

  1. Komut satırı bayrakları (en yüksek öncelik)
  2. Ortam değişkenleri (MYCLI_API_KEY)
  3. ~/.config/mycli/config.yaml gibi kullanıcı dizini

Viper Cobra ile aynı ekosistemden gelir; ancak küçük araçlarda sadece os.Getenv ve bir kez okunan YAML/JSON yeterli olabilir. Bağımlılık sayısını proje büyüklüğüne göre seçin.


7. Çapraz derleme (cross-compile)

Go, tek makineden birden fazla platform için binary üretmenize izin verir:

GOOS=linux GOARCH=amd64 go build -o mycli-linux-amd64 .
GOOS=darwin GOARCH=arm64 go build -o mycli-darwin-arm64 .
GOOS=windows GOARCH=amd64 go build -o mycli-windows-amd64.exe .

CI pipeline’ında bu komutlarla artefact üretmek yaygındır.


8. Test stratejisi

  • Birim testleri: iş mantığını os.Args veya global state’e bağlamadan test edin.
  • Entegrasyon: exec.Command ile derlenmiş binary’yi çalıştırmak maliyetlidir ama kritik akışlar için değerlidir.
  • Golden file: stdout çıktısını beklenen dosyayla karşılaştırma.

Örnek: RunE içinde fmt.Printf yerine io.Writer enjekte edilen bir Run(out io.Writer, args []string) error imzası test edilebilirliği ciddi artırır.


9. Dağıtım: GoReleaser

GoReleaser ile Git tag attığınızda otomatik olarak çoklu platform binary’leri, checksum’lar ve bazen paket yöneticisi tarifleri üretilir. Özellikle açık kaynak CLI’lar için vakit kazandırır.

Minimum düşünceyle bile go install github.com/kullanici/mycli@latest ile modül yolu üzerinden kurulum sunabilirsiniz (Go 1.17+).


10. Kullanıcı deneyimi ipuçları

  • Yardım metinleri: Short ve Long alanlarını doldurun; örnek kullanım verin.
  • Sürüm bilgisi: ldflags ile -X main.version= ile git commit’ten enjekte edilen sürüm string’i kullanın.
  • Renk: Güzel görünür ama log dosyasına yönlendirmede ANSI kodları kirletir; TTY kontrolü yapın.
  • İlerleme: Uzun işlerde basit bir sayaç veya progressbar tarzı kütüphaneler faydalıdır; ancak stdout’a makine çıktısı yazıyorsanız ilerleme çubuğunu stderr’e alın.

Özet kontrol listesi

  • stdout / stderr ayrımı net mi?
  • Hatalar kullanıcıya anlaşılır mesajla mı gidiyor?
  • Çıkış kodları belgelendi mi?
  • -h / --help anlamlı mı?
  • Cross-build ve CI ile binary üretimi otomatik mi?

Go ile CLI geliştirmek, doğru soyutlama ile hem hızlı prototip hem de yıllarca sürecek bir iç araç için sağlam bir temel sunar. Önce flag ile başlayın; karmaşıklık arttıkça Cobra’ya geçin; dağıtımı otomatikleştirin.

Komut satırı sizin arayüzünüz — kısa, net ve öngörülebilir tutun.