Go ile CLI Geliştirme: Komut Satırı Araçlarına Derinlemesine Rehber
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:
- Komut satırı bayrakları (en yüksek öncelik)
- Ortam değişkenleri (
MYCLI_API_KEY) ~/.config/mycli/config.yamlgibi 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.Argsveya global state’e bağlamadan test edin. - Entegrasyon:
exec.Commandile 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:
ShortveLongalanlarını doldurun; örnek kullanım verin. - Sürüm bilgisi:
ldflagsile-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
progressbartarzı 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/stderrayrımı net mi? - Hatalar kullanıcıya anlaşılır mesajla mı gidiyor?
- Çıkış kodları belgelendi mi?
-
-h/--helpanlamlı 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.
İlgili Yazılar
gRPC vs REST: Ne Zaman Hangisini Kullanmalısın? Go ile Karşılaştırmalı Rehber
Mikroservislerde gRPC ve REST farkları; protobuf, HTTP/2, tarayıcı uyumu ve Go örnekleri. REST ile Go servis karşılaştırması için iç link.
Redis ile Rate Limiting: Go'da Token Bucket ve Sliding Window Implementasyonu
golang rate limiting için Redis tabanlı token bucket ve sliding window algoritmaları; Lua script, atomicity ve üretimde dikkat edilecek noktalar.
Go ile JWT Authentication: Access Token, Refresh Token ve Güvenli Saklama
JWT üretimi ve doğrulaması Go'da; kısa ömürlü access token, refresh token rotasyonu, HttpOnly cookie ve yaygın hatalar.