Takip et

Go’da flag Paketi Nasıl Kullanılır: Komut Satırı Argümanlarını Yönetmek

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üman

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:

  1. Bayrağın adı (örneğin, “name”, “port”, “verbose”).
  2. Bayrağın varsayılan değeri. Kullanıcı bu bayrağı belirtmezse kullanılacak değer budur.
  3. Bayrağın kısa açıklaması. Bu açıklama, flag.PrintDefaults() veya -h/--help ile 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: false
  • go run main.go --name "Go Programcısı" --port 9000 -verbose: Merhaba, Go Programcısı! Uygulama 9000 portunda çalışıyor. Detaylı mod: true
  • go 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) *int
  • flag.IntVar(p *int, name string, value int, usage string)
  • Benzer şekilde flag.Int64, flag.IntVar64, flag.Uint, flag.UintVar, flag.Uint64, flag.UintVar64 fonksiyonları 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) *bool
  • flag.BoolVar(p *bool, name string, value bool, usage string)

Boolean bayraklar özel bir şekilde işlenir:

  • --flagname veya -flagname: Varsayılan değeri false ise true yapar.
  • --flagname=true veya --flagname=false: Değeri açıkça belirtir.
  • --flagname true (boşluklu): Bu kullanım flag paketi 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.Duration
  • flag.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 --force
  • go run main.go remove --id 123
  • go 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ı: mysecretkey
  • APP_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ü --input zorunlu.
  • 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.txt dosyası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.FlagSet ile alt komutları yönetmek mümkün olsa da, bu sürecin çoğunu manuel olarak yapmanız gerekir (argümanları dilimlemek, doğru FlagSet‘i seçmek vb.).
  • Bayrak Grupları: flag paketi, 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, flag paketi 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, Hugo gibi 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. flag paketine göre daha fazla özellik sunar ve alt komutları daha kolay yönetir. Özellikle daha az karmaşık ancak flag‘den daha fazlasını isteyen projeler için iyi bir seçenektir.

    import "github.com/urfave/cli/v2"
  • pflag: flag paketi ile API uyumluluğunu korurken, GNU getopt stili bayrakları (örneğin, uzun form --name ve kısa form -n, tek tireli uzun formlar) destekleyen bir kütüphanedir. Eğer flag paketinin 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.

Yorumlar
İçeriği beğendiniz mi? Bir tartışma başlatın veya görüşlerinizi paylaşın.
Yorum Yaz

Bir yanıt yazın

E-posta adresiniz yayınlanmayacak. Gerekli alanlar * ile işaretlenmişlerdir

Gönder

E-posta Bülteni
Yazılım Topluluğuna Katılın
En son güncellemeleri, yaratıcı ipuçlarını ve özel kaynakları doğrudan e-posta kutunuza alın. Tasarım ve inovasyonun geleceğini birlikte keşfedelim.
Mobil sürümden çık