Go’da flag Paketi Nasıl Kullanılır: Komut Satırı Argümanlarını Yönetmek
Giriş
Go programlama dili, uygulamaların komut satırı argümanlarını kolayca ayrıştırması ve yönetmesi için standart kütüphanesinde güçlü ve kullanımı basit bir paket olan flag paketini sunar. Modern uygulamaların çoğu, kullanıcıdan veya otomasyon sistemlerinden gelen girdileri işlemek için komut satırı argümanlarına ihtiyaç duyar. Bu argümanlar genellikle yapılandırma ayarlarını (örneğin, veritabanı bağlantı dizeleri), çalışma zamanı seçeneklerini (örneğin, hata ayıklama modu) veya belirli görevleri (örneğin, dosya yolları) belirtmek için kullanılır. flag paketi, bu tür girdileri tanımlamanıza, varsayılan değerler atamanıza, açıklamalar eklemenize ve programınızın bu argümanları güvenli bir şekilde ayrıştırmasına olanak tanır.
Bu makalede, flag paketinin temelden ileri seviyeye kadar nasıl kullanılacağını ayrıntılı olarak inceleyeceğiz. Değişken türlerine göre bayrak tanımlamadan, özel bayrak türleri oluşturmaya, hata yönetimine ve alt komutlar için FlagSet kullanımına kadar birçok konuyu ele alacağız. Amacımız, Go projelerinizde komut satırı argümanlarını etkin bir şekilde yönetmek için ihtiyacınız olan tüm bilgileri size sunmaktır.
Temel Kullanım
flag paketini kullanmaya başlamak oldukça basittir. İlk adım, programınızın beklediği bayrakları (argümanları) tanımlamak ve ardından bu bayrakları ayrıştırmak için flag.Parse() fonksiyonunu çağırmaktır.
Değişken Tanımlama ve Bayrak Bağlama
flag paketi, farklı veri türleri için bayraklar tanımlamanıza olanak tanıyan bir dizi fonksiyon sunar. En yaygın kullanılanlar flag.String, flag.Int ve flag.Bool‘dur. Bu fonksiyonlar, bir bayrak tanımlar ve bu bayrağın değerini tutacak bir değişkenin işaretçisini (pointer) döndürür.
Her bir fonksiyon genellikle dört argüman alır:
- Bayrağın adı (örneğin, “name”, “port”, “verbose”).
- Bayrağın varsayılan değeri. Kullanıcı bu bayrağı belirtmezse kullanılacak değer budur.
- Bayrağın kısa açıklaması. Bu açıklama,
flag.PrintDefaults()veya-h/--helpile otomatik olarak oluşturulan kullanım mesajında gösterilir.
İşte basit bir örnek:
package main
import (
"flag"
"fmt"
)
func main() {
// String tipi bir bayrak tanımlama
name := flag.String("name", "Dünya", "Selam verilecek isim")
// Integer tipi bir bayrak tanımlama
port := flag.Int("port", 8080, "Uygulamanın çalışacağı port numarası")
// Boolean tipi bir bayrak tanımlama
verbose := flag.Bool("verbose", false, "Detaylı çıktı göstermek için")
// flag.Parse() çağrılmadan önce bayrakların varsayılan değerleri vardır.
fmt.Printf("Başlangıç değerleri: name=%s, port=%d, verbose=%t\n", name, port, *verbose)
// Bayrakları ayrıştırma (komut satırı argümanlarını işleme)
flag.Parse()
// flag.Parse() çağrıldıktan sonra bayrakların değerlerine erişebiliriz.
// Döndürülen işaretçiyi (pointer) dereference etmeyi unutmayın.
fmt.Printf("Merhaba, %s! Uygulama %d portunda çalışıyor. Detaylı mod: %t\n", name, port, *verbose)
}
Bu kodu derleyip çalıştırdığınızda:
go run main.go:Merhaba, Dünya! Uygulama 8080 portunda çalışıyor. Detaylı mod: falsego run main.go --name "Go Programcısı" --port 9000 -verbose:Merhaba, Go Programcısı! Uygulama 9000 portunda çalışıyor. Detaylı mod: truego run main.go -h: Tanımladığınız bayrakların açıklamalarıyla birlikte bir kullanım mesajı görürsünüz.
Alternatif olarak, mevcut bir değişkeni bayrağa bağlamak için flag.StringVar, flag.IntVar, flag.BoolVar gibi fonksiyonları kullanabilirsiniz. Bu, özellikle yapılandırma nesneleriyle çalışırken faydalı olabilir:
package main
import (
"flag"
"fmt"
)
func main() {
var name string
var port int
var verbose bool
flag.StringVar(&name, "name", "Dünya", "Selam verilecek isim")
flag.IntVar(&port, "port", 8080, "Uygulamanın çalışacağı port numarası")
flag.BoolVar(&verbose, "verbose", false, "Detaylı çıktı göstermek için")
flag.Parse()
fmt.Printf("Merhaba, %s! Uygulama %d portunda çalışıyor. Detaylı mod: %t\n", name, port, verbose)
}
Her iki yöntem de aynı sonucu verir; tercih genellikle kişisel kodlama stiline veya mevcut değişken yapısına bağlıdır.
Bayrakları Ayrıştırma (flag.Parse())
flag.Parse() fonksiyonu, flag paketinin kalbidir. Bu fonksiyon, komut satırı argümanlarını tarar, tanımlanmış bayrakları bulur ve karşılık gelen değişkenlerin değerlerini günceller. Tüm bayrak tanımlamaları yapıldıktan sonra ve bayrak değerlerine erişmeden önce flag.Parse() çağrılmalıdır. Eğer flag.Parse() çağrılmazsa, bayrak değişkenleri varsayılan değerlerini korur ve komut satırında belirtilen değerler dikkate alınmaz.
flag.Parse() çağrıldığında, os.Args dilimini (slice) ayrıştırmaya başlar. os.Args[0] programın adıdır ve flag paketi bunu göz ardı eder. Kalan argümanlar, tanımlanmış bayraklarla eşleştirilir. Eğer bilinmeyen bir bayrak veya geçersiz bir değer formatı ile karşılaşılırsa, flag paketi varsayılan olarak bir hata mesajı yazdırır ve programdan os.Exit(2) ile çıkar. Bu davranış flag.Usage değişkeni özelleştirilerek değiştirilebilir.
flag.Parse() fonksiyonundan sonra, bayraklar tarafından işlenmeyen tüm kalan komut satırı argümanlarına flag.Args() fonksiyonu ile erişilebilir. Bu, özellikle bir komutun hem bayrakları hem de pozisyonel argümanları (örneğin, dosya adları veya kimlikler) kabul ettiği senaryolarda kullanışlıdır.
package main
import (
"flag"
"fmt"
)
func main() {
name := flag.String("name", "Anonim", "İşlem yapacak kişinin adı")
flag.Parse()
fmt.Printf("İşlem yapan: %s\n", *name)
// Kalan argümanlara erişim
if flag.NArg() > 0 {
fmt.Println("Kalan argümanlar:")
for i, arg := range flag.Args() {
fmt.Printf(" %d: %s\n", i+1, arg)
}
} else {
fmt.Println("Hiç kalan argüman yok.")
}
}
Çalıştırma örnekleri:
go run main.go:İşlem yapan: Anonim,Hiç kalan argüman yok.go run main.go --name "Alice":İşlem yapan: Alice,Hiç kalan argüman yok.go run main.go file1.txt file2.txt --name "Bob":İşlem yapan: Bob,Kalan argümanlar: 1: file1.txt, 2: file2.txt(Bayraklar her zaman argümanlardan önce gelmeli, aksi takdirde bayrak olmayanlar argüman olarak algılanır. Doğrusu:go run main.go --name "Bob" file1.txt file2.txt)
Gördüğünüz gibi, flag.Parse() tüm bayrakları işler ve ardından kalan her şeyi flag.Args()‘a bırakır.
Bayrak Türleri ve Kullanım Senaryoları
flag paketi, Go’nun temel veri türlerine karşılık gelen birçok hazır bayrak türü sunar. Ayrıca, özel veri türleriniz için kendi bayrak türlerinizi de tanımlayabilirsiniz.
String Bayrakları
En sık kullanılan bayrak türlerinden biridir. Metin tabanlı girdiler için kullanılır.
flag.String(name string, value string, usage string) *string: Yeni bir string bayrak tanımlar ve varsayılan değeriyle birlikte bir işaretçi döndürür.flag.StringVar(p *string, name string, value string, usage string): Mevcut bir string değişkenini bir bayrağa bağlar.
package main
import (
"flag"
"fmt"
)
func main() {
var configPath string
flag.StringVar(&configPath, "config", "/etc/app/config.json", "Uygulama yapılandırma dosyasının yolu")
logLevel := flag.String("log-level", "info", "Uygulama günlük seviyesi (debug, info, warn, error)")
flag.Parse()
fmt.Printf("Yapılandırma dosyası: %s\n", configPath)
fmt.Printf("Günlük seviyesi: %s\n", *logLevel)
}
Kullanım: go run main.go --config /opt/app/myconfig.json --log-level debug
Integer Bayrakları
Sayısal değerler için kullanılır. int, int64, uint, uint64 gibi farklı tam sayı türleri için fonksiyonlar mevcuttur.
flag.Int(name string, value int, usage string) *intflag.IntVar(p *int, name string, value int, usage string)- Benzer şekilde
flag.Int64,flag.IntVar64,flag.Uint,flag.UintVar,flag.Uint64,flag.UintVar64fonksiyonları da bulunur.
package main
import (
"flag"
"fmt"
)
func main() {
maxWorkers := flag.Int("workers", 4, "Aynı anda çalışacak maksimum işçi sayısı")
timeoutSeconds := flag.Uint("timeout", 30, "İşlemler için saniye cinsinden zaman aşımı")
flag.Parse()
fmt.Printf("Maksimum işçi sayısı: %d\n", *maxWorkers)
fmt.Printf("Zaman aşımı (saniye): %d\n", *timeoutSeconds)
}
Kullanım: go run main.go --workers 8 --timeout 60
Boolean Bayrakları
Genellikle bir özelliğin açılıp kapanması gibi ikili durumlar için kullanılır (true/false).
flag.Bool(name string, value bool, usage string) *boolflag.BoolVar(p *bool, name string, value bool, usage string)
Boolean bayraklar özel bir şekilde işlenir:
--flagnameveya-flagname: Varsayılan değerifalseisetrueyapar.--flagname=trueveya--flagname=false: Değeri açıkça belirtir.--flagname true(boşluklu): Bu kullanımflagpaketi için geçerli değildir.
package main
import (
"flag"
"fmt"
)
func main() {
debugMode := flag.Bool("debug", false, "Hata ayıklama modunu etkinleştirir")
dryRun := flag.Bool("dry-run", false, "Gerçek değişiklikler yapmadan işlemi simüle eder")
flag.Parse()
if *debugMode {
fmt.Println("Hata ayıklama modu etkin.")
}
if *dryRun {
fmt.Println("Deneme çalıştırması etkin. Gerçek değişiklikler yapılmayacak.")
}
}
Kullanım:
go run main.go --debug:Hata ayıklama modu etkin.go run main.go --debug=true --dry-run=false:Hata ayıklama modu etkin.go run main.go -dry-run:Deneme çalıştırması etkin. Gerçek değişiklikler yapılmayacak.
Süre (Duration) Bayrakları
time.Duration tipi için bayraklar tanımlamanıza olanak tanır. Süre değerleri, “1s”, “10m”, “1h30m” gibi Go’nun time.ParseDuration formatında belirtilir.
flag.Duration(name string, value time.Duration, usage string) *time.Durationflag.DurationVar(p *time.Duration, name string, value time.Duration, usage string)
package main
import (
"flag"
"fmt"
"time"
)
func main() {
refreshInterval := flag.Duration("interval", 5*time.Second, "Yenileme aralığı (örn: 1s, 30m, 2h)")
flag.Parse()
fmt.Printf("Veriler her %s'de bir yenilenecek.\n", *refreshInterval)
}
Kullanım: go run main.go --interval 1m30s (1 dakika 30 saniye)
Özel Bayrak Türleri (flag.Value Arayüzü)
flag paketi, flag.Value arayüzünü uygulayarak kendi özel bayrak türlerinizi tanımlamanıza olanak tanır. Bu arayüz iki metot gerektirir:
Set(string) error: Bir string değeri alarak bayrağın değerini ayarlar. Ayrıştırma sırasında kullanılır.String() string: Bayrağın mevcut değerini string olarak döndürür. Varsayılan değerler ve kullanım mesajları için kullanılır.
Bu, özellikle virgülle ayrılmış listeler, özel yapılandırılmış stringler veya karmaşık veri yapılarını doğrudan komut satırından almak istediğinizde çok kullanışlıdır.
Örnek: Virgülle ayrılmış bir string listesini ayrıştırmak için özel bir bayrak:
package main
import (
"flag"
"fmt"
"strings"
)
// stringList, virgülle ayrılmış stringlerin listesini tutar.
type stringList []string
// String metodu, stringList'i string olarak temsil eder.
func (s *stringList) String() string {
return strings.Join(*s, ",")
}
// Set metodu, string değeri alıp listeye ekler.
func (s *stringList) Set(value string) error {
*s = strings.Split(value, ",")
return nil
}
func main() {
var tags stringList
// Özel bayrağı tanımlamak için flag.Var() kullanılır.
flag.Var(&tags, "tags", "Virgülle ayrılmış etiketler listesi")
var userIDs stringList
flag.Var(&userIDs, "users", "Virgülle ayrılmış kullanıcı ID'leri")
flag.Parse()
fmt.Printf("Etiketler: %v\n", tags)
fmt.Printf("Kullanıcı ID'leri: %v\n", userIDs)
}
Kullanım: go run main.go --tags "go,programming,cli" --users "101,205,330"
Bu örnekte, stringList tipi flag.Value arayüzünü uyguladığı için, flag.Var() fonksiyonu ile doğrudan bir bayrak olarak kullanılabilir. Bu, flag paketinin esnekliğini gösteren güçlü bir özelliktir.
Gelişmiş Kullanım ve İpuçları
flag paketi, temel kullanımın ötesinde, daha karmaşık senaryoları yönetmek için de özellikler sunar.
Hata Yönetimi ve Kullanım Mesajları
Yanlış bir bayrak adı veya geçersiz bir bayrak değeri belirtildiğinde, flag paketi varsayılan olarak bir hata mesajı yazdırır ve programdan çıkar. Bu davranış genellikle istenen bir durum olsa da, bazen özelleştirmek isteyebilirsiniz.
Varsayılan kullanım mesajını (usage message) özelleştirmek için flag.Usage değişkenine kendi fonksiyonunuzu atayabilirsiniz:
package main
import (
"flag"
"fmt"
"os"
)
func main() {
name := flag.String("name", "Anonim", "Selam verilecek isim")
port := flag.Int("port", 8080, "Uygulamanın çalışacağı port numarası")
// flag.Usage'ı özelleştirme
flag.Usage = func() {
fmt.Fprintf(os.Stderr, "Kullanım: %s [seçenekler] [argümanlar...]\n", os.Args[0])
fmt.Fprintln(os.Stderr, "Bu uygulama basit bir selamlayıcıdır.")
fmt.Fprintln(os.Stderr, "Seçenekler:")
flag.PrintDefaults() // Tanımlanmış tüm bayrakların varsayılanlarını yazdırır
fmt.Fprintln(os.Stderr, "\nÖrnek: myapp --name \"Ali\" --port 9000 file.txt")
}
flag.Parse()
fmt.Printf("Merhaba, %s! Uygulama %d portunda çalışıyor.\n", name, port)
if flag.NArg() > 0 {
fmt.Printf("İşlenecek dosyalar: %v\n", flag.Args())
}
}
Şimdi go run main.go -h veya geçersiz bir bayrak belirtildiğinde, kendi özel kullanım mesajınız gösterilecektir.
Ayrıca, flag paketi hata durumunda programdan çıkmak yerine bir hata döndürmesi için flag.ExitOnError yerine flag.ContinueOnError veya flag.PanicOnError seçeneklerini kullanabilirsiniz. Bu seçenekler flag.FlagSet yapısının bir parçasıdır, ancak varsayılan flag.CommandLine için de ayarlanabilir:
package main
import (
"flag"
"fmt"
"os"
)
func main() {
// Hata durumunda programdan çıkmak yerine hata döndürmesini sağlarız.
flag.CommandLine.SetOutput(os.Stdout) // Hata mesajlarını stdout'a yönlendir
flag.CommandLine.Usage = func() { // Hata durumunda da kullanım mesajını gösteririz
fmt.Printf("Kullanım: %s [seçenekler]\n", os.Args[0])
flag.PrintDefaults()
}
flag.CommandLine.Init(os.Args[0], flag.ContinueOnError)
name := flag.String("name", "Anonim", "Selam verilecek isim")
err := flag.CommandLine.Parse(os.Args[1:]) // os.Args[1:] ile argümanları ayrıştır
if err != nil {
fmt.Printf("Hata oluştu: %v\n", err)
return
}
fmt.Printf("Merhaba, %s!\n", *name)
}
Bu örnekte, flag.ContinueOnError kullanıldığında, flag.CommandLine.Parse() bir hata döndürebilir ve programın hemen çıkması engellenir. Bu, özellikle karmaşık CLI uygulamalarında hata işleme üzerinde daha fazla kontrol sağlamak için kullanışlıdır.
Kalan Argümanlara Erişim (flag.Args())
flag.Parse() çağrıldıktan sonra, bayrak olarak tanımlanmayan ve ayrıştırılmayan tüm komut satırı argümanları flag.Args() fonksiyonu aracılığıyla bir string dilimi olarak döndürülür. flag.NArg() ise kalan argümanların sayısını verir.
package main
import (
"flag"
"fmt"
)
func main() {
count := flag.Int("count", 1, "Tekrar sayısı")
flag.Parse()
fmt.Printf("Tekrar sayısı: %d\n", *count)
if flag.NArg() > 0 {
fmt.Printf("İşlenecek öğeler: %v\n", flag.Args())
} else {
fmt.Println("İşlenecek öğe belirtilmedi.")
}
}
Kullanım: go run main.go --count 5 item1 item2 item3
Çıktı: Tekrar sayısı: 5, İşlenecek öğeler: [item1 item2 item3]
flag.FlagSet ile Bayrak Grupları Oluşturma
Tek bir program içinde birden fazla bağımsız bayrak grubuna ihtiyacınız olduğunda, örneğin alt komutlar (subcommands) için (git commit, go build gibi), flag.FlagSet yapısını kullanabilirsiniz. Her FlagSet kendi bayrak setini yönetir ve kendi Parse() metoduna sahiptir.
package main
import (
"flag"
"fmt"
"os"
)
func main() {
// Ana bayraklar
verbose := flag.Bool("verbose", false, "Genel detaylı çıktı")
// "add" alt komutu için FlagSet
addCmd := flag.NewFlagSet("add", flag.ExitOnError)
addFile := addCmd.String("file", "", "Eklenecek dosya adı")
addForce := addCmd.Bool("force", false, "Ekleme işlemini zorla")
// "remove" alt komutu için FlagSet
removeCmd := flag.NewFlagSet("remove", flag.ExitOnError)
removeID := removeCmd.Int("id", 0, "Kaldırılacak öğenin ID'si")
flag.Parse() // Ana bayrakları ayrıştır
if *verbose {
fmt.Println("Genel detaylı mod etkin.")
}
// Kalan argümanlara bakarak alt komutu belirle
if flag.NArg() == 0 {
fmt.Println("Lütfen bir komut belirtin: add veya remove")
flag.Usage()
return
}
switch flag.Args()[0] {
case "add":
addCmd.Parse(flag.Args()[1:]) // "add" komutunun argümanlarını ayrıştır
if *addFile == "" {
fmt.Println("Hata: add komutu için --file belirtilmeli.")
addCmd.Usage()
os.Exit(1)
}
fmt.Printf("Dosya ekleniyor: %s (Zorla: %t)\n", addFile, addForce)
case "remove":
removeCmd.Parse(flag.Args()[1:]) // "remove" komutunun argümanlarını ayrıştır
if *removeID == 0 {
fmt.Println("Hata: remove komutu için --id belirtilmeli.")
removeCmd.Usage()
os.Exit(1)
}
fmt.Printf("Öğe kaldırılıyor: ID %d\n", *removeID)
default:
fmt.Printf("Bilinmeyen komut: %s\n", flag.Args()[0])
flag.Usage()
os.Exit(1)
}
}
Kullanım:
go run main.go add --file mydata.txt --forcego run main.go remove --id 123go run main.go --verbose add --file newfile.txt
FlagSet kullanımı, karmaşık CLI uygulamalarını daha modüler ve yönetilebilir hale getirir.
Çevre Değişkenleri ile Entegrasyon
flag paketi doğrudan çevre değişkenlerinden değer okuma özelliği sunmaz. Ancak, bu işlevselliği os.Getenv kullanarak kolayca entegre edebilirsiniz:
package main
import (
"flag"
"fmt"
"os"
"strconv"
)
func main() {
// Varsayılan değeri çevre değişkeninden al
defaultPort := 8080
if envPort := os.Getenv("APP_PORT"); envPort != "" {
if p, err := strconv.Atoi(envPort); err == nil {
defaultPort = p
}
}
port := flag.Int("port", defaultPort, "Uygulamanın çalışacağı port numarası (APP_PORT çevre değişkeni ile de ayarlanabilir)")
// Varsayılan değeri doğrudan çevre değişkeninden alan string bayrak
defaultAPIKey := os.Getenv("API_KEY")
apiKey := flag.String("api-key", defaultAPIKey, "API anahtarı (API_KEY çevre değişkeni ile de ayarlanabilir)")
flag.Parse()
fmt.Printf("Uygulama %d portunda çalışıyor.\n", *port)
if *apiKey != "" {
fmt.Printf("API Anahtarı: %s\n", *apiKey)
} else {
fmt.Println("API Anahtarı belirtilmedi.")
}
}
Kullanım:
go run main.go:Uygulama 8080 portunda çalışıyor. API Anahtarı belirtilmedi.APP_PORT=9000 API_KEY=mysecretkey go run main.go:Uygulama 9000 portunda çalışıyor. API Anahtarı: mysecretkeyAPP_PORT=9000 go run main.go --port 9090: Komut satırı argümanları her zaman çevre değişkenlerini ezer. Bu durumda port 9090 olur.
Varsayılan Değerleri Değiştirme
Bayrakları tanımlarken bir varsayılan değer atarsınız. Ancak bazen programatik olarak bir bayrağın varsayılan değerini değiştirmek isteyebilirsiniz. Bunu flag.Lookup() ve flag.Value.Set() metodlarını kullanarak yapabilirsiniz:
package main
import (
"flag"
"fmt"
)
func main() {
name := flag.String("name", "Varsayılan", "Selam verilecek isim")
// Programatik olarak varsayılan değeri değiştirme
if f := flag.Lookup("name"); f != nil {
f.Value.Set("YeniVarsayılan") // Set metodu string bekler
}
flag.Parse()
fmt.Printf("Merhaba, %s!\n", *name)
}
Bu örnekte, eğer komut satırında --name belirtilmezse, yeni varsayılan değer olan “YeniVarsayılan” kullanılacaktır.
Uygulama Örneği: Basit Bir Komut Satırı Aracı
Şimdiye kadar öğrendiklerimizi bir araya getirerek, basit bir metin dosyası işleme aracı oluşturalım. Bu araç, bir giriş dosyasını okuyacak, isteğe bağlı olarak büyük harfe çevirecek ve çıktı dosyasını belirleyecek.
package main
import (
"bufio"
"flag"
"fmt"
"io"
"os"
"strings"
)
func main() {
// Bayrakları tanımlama
inputFilePath := flag.String("input", "", "Giriş metin dosyasının yolu (zorunlu)")
outputFilePath := flag.String("output", "", "Çıktı metin dosyasının yolu (varsayılan: stdout)")
uppercase := flag.Bool("uppercase", false, "Çıktıyı büyük harfe çevir")
linePrefix := flag.String("prefix", "", "Her satırın başına eklenecek ön ek")
// Kullanım mesajını özelleştirme
flag.Usage = func() {
fmt.Fprintf(os.Stderr, "Kullanım: %s [seçenekler]\n", os.Args[0])
fmt.Fprintln(os.Stderr, "Basit bir metin dosyası işleme aracı.")
fmt.Fprintln(os.Stderr, "Seçenekler:")
flag.PrintDefaults()
fmt.Fprintln(os.Stderr, "\nÖrnek: mytexttool --input data.txt --output processed.txt --uppercase")
}
flag.Parse()
// Giriş dosyası zorunlu
if *inputFilePath == "" {
fmt.Fprintln(os.Stderr, "Hata: --input bayrağı zorunludur.")
flag.Usage()
os.Exit(1)
}
// Giriş dosyasını aç
inputFile, err := os.Open(*inputFilePath)
if err != nil {
fmt.Fprintf(os.Stderr, "Giriş dosyası açılamadı: %v\n", err)
os.Exit(1)
}
defer inputFile.Close()
// Çıktı hedefini belirle (dosya veya stdout)
var outputWriter io.Writer
if *outputFilePath != "" {
outputFile, err := os.Create(*outputFilePath)
if err != nil {
fmt.Fprintf(os.Stderr, "Çıktı dosyası oluşturulamadı: %v\n", err)
os.Exit(1)
}
defer outputFile.Close()
outputWriter = outputFile
} else {
outputWriter = os.Stdout
}
scanner := bufio.NewScanner(inputFile)
writer := bufio.NewWriter(outputWriter)
defer writer.Flush() // Çıkıştan önce tüm tamponu boşalt
lineNum := 0
for scanner.Scan() {
lineNum++
line := scanner.Text()
// Ön ek ekle
if *linePrefix != "" {
line = *linePrefix + line
}
// Büyük harfe çevir
if *uppercase {
line = strings.ToUpper(line)
}
// Çıktıya yaz
_, err := writer.WriteString(line + "\n")
if err != nil {
fmt.Fprintf(os.Stderr, "Çıktıya yazarken hata oluştu: %v\n", err)
os.Exit(1)
}
}
if err := scanner.Err(); err != nil {
fmt.Fprintf(os.Stderr, "Giriş dosyasını okurken hata oluştu: %v\n", err)
os.Exit(1)
}
fmt.Printf("İşlem tamamlandı. %d satır işlendi.\n", lineNum)
}
Bu aracı kullanmak için önce bir data.txt dosyası oluşturalım:
Merhaba Go
Bu bir deneme metnidir.
Flag paketi çok kullanışlı.
Şimdi aracı çalıştıralım:
go run main.go: Hata verecektir çünkü--inputzorunlu.go run main.go --input data.txt: Çıktıyı standart çıktıya yazar.go run main.go --input data.txt --uppercase: Çıktıyı büyük harfe çevirir ve standart çıktıya yazar.go run main.go --input data.txt --output processed.txt --prefix "LINE: ": Çıktıyıprocessed.txtdosyasına yazar ve her satırın başına “LINE: ” ekler.
Bu örnek, flag paketinin çeşitli özelliklerini (string, bool bayraklar, zorunlu bayrak kontrolü, özel kullanım mesajı) gerçek bir senaryoda nasıl birleştirebileceğinizi gösterir.
Sınırlamalar ve Alternatifler
flag paketi, Go’da komut satırı argümanlarını yönetmek için basit ve etkili bir çözüm sunar. Ancak, özellikle çok karmaşık CLI uygulamaları için bazı sınırlamaları vardır:
- Alt Komutlar için manuel yönetim:
flag.FlagSetile alt komutları yönetmek mümkün olsa da, bu sürecin çoğunu manuel olarak yapmanız gerekir (argümanları dilimlemek, doğruFlagSet‘i seçmek vb.). - Bayrak Grupları:
flagpaketi, mantıksal olarak birbiriyle ilişkili bayrakları gruplamak için yerleşik bir mekanizma sunmaz. - Gelişmiş Doğrulama: Bayrak değerleri için karmaşık doğrulama kuralları (örneğin, bir sayının belirli bir aralıkta olması) genellikle manuel olarak uygulanmalıdır.
- Otomatik Tamamlama (Autocompletion): Gelişmiş CLI kütüphaneleri genellikle kabuk otomatik tamamlama özellikleri sunarken,
flagpaketi bu tür bir destek sağlamaz.
Bu sınırlamalarla karşılaştığınızda veya daha zengin özelliklere sahip bir CLI deneyimi oluşturmak istediğinizde, Go topluluğunda popüler olan alternatif kütüphanelere yönelebilirsiniz:
-
Cobra: En popüler CLI kütüphanelerinden biridir. Güçlü alt komut desteği, iç içe komutlar, bayrak grupları, otomatik tamamlama ve kolay yapılandırma gibi birçok gelişmiş özellik sunar.
kubectl,Docker,Hugogibi birçok büyük proje Cobra kullanır.import "github.com/spf13/cobra" -
urfave/cli (eski adıyla
cli): Basit ve hızlı CLI uygulamaları oluşturmak için tasarlanmıştır.flagpaketine göre daha fazla özellik sunar ve alt komutları daha kolay yönetir. Özellikle daha az karmaşık ancakflag‘den daha fazlasını isteyen projeler için iyi bir seçenektir.import "github.com/urfave/cli/v2" -
pflag:
flagpaketi ile API uyumluluğunu korurken, GNUgetoptstili bayrakları (örneğin, uzun form--nameve kısa form-n, tek tireli uzun formlar) destekleyen bir kütüphanedir. Eğerflagpaketinin basitliğini seviyor ancak daha fazla bayrak stili esnekliği istiyorsanız iyi bir alternatiftir.import "github.com/spf13/pflag"
Genel olarak, uygulamanızın komut satırı arayüzü basitse ve yalnızca birkaç bayrağa ihtiyacınız varsa, flag paketi mükemmel bir seçimdir. Ancak, uygulamanız büyüdükçe ve alt komutlar, karmaşık bayrak etkileşimleri veya zengin bir kullanıcı deneyimi gerektiren daha gelişmiş bir CLI’ye ihtiyaç duydukça, Cobra veya urfave/cli gibi alternatifleri değerlendirmek akıllıca olacaktır.
Sonuç
Go’nun flag paketi, komut satırı argümanlarını Go programlarında yönetmek için basit, etkili ve standart bir yöntem sunar. Temel veri türleri için bayrak tanımlamadan, özel bayrak türleri oluşturmaya, hata yönetimine ve FlagSet ile alt komutları desteklemeye kadar geniş bir yelpazede işlevsellik sağlar. Bu makalede ele aldığımız konular, Go projelerinizde komut satırı argümanlarını güçlü bir şekilde kullanmanız için size sağlam bir temel sunmaktadır.
Doğru kullanıldığında, flag paketi uygulamalarınızın daha esnek, yapılandırılabilir ve kullanıcı dostu olmasını sağlar. Komut satırı arayüzünüzün karmaşıklığı arttığında alternatif kütüphaneleri değerlendirmek faydalı olsa da, çoğu durumda flag paketi ihtiyacınız olan her şeyi fazlasıyla karşılayacaktır. Go geliştiricileri olarak, bu temel paketi ustaca kullanmak, sağlam ve profesyonel uygulamalar oluşturma becerinizin önemli bir parçasıdır.
