1. flag paketi niyə lazımdır, əgər os.Args varsa?
CLI proqramı yazanda siz demək olar ki, həmişə istəyirsiniz ki, istifadəçi davranışı tənzimləyə bilsin: debug rejimini yandırsın, limit təyin etsin, çıxış formatını seçsin. Əlbəttə, os.Args-ı əl ilə də təhlil etmək olar, amma bu, tezliklə tətil sonrası çamadanı boşaltmağa bənzəməyə başlayır: “burada əlavə üç paket haradandır və niyə corablar soyuducudadır?”. flag paketi Go-da flaq təhlilinin standart yoludur; vahid sintaksis verir və rutin işin bir hissəsini aradan qaldırır.
Fundamental ideya budur: os.Args sadəcə OS-nin ötürdüyü xam []string-dir, flag isə onu lazım olan tiplərdə başadüşülən qiymətlərə çevirə bilir. Nəticədə siz daimi parse şənliyi yox, səliqəli string/int/bool alırsınız: “burada -limit=10 idi, yoxsa -limit 10, bəlkə də -limit ten?”.
Daxildə flag yenə də işə salınan arqumentlərə baxır (defolt olaraq bu, os.Args[1:]-dir), amma bunu mərkəzləşdirilmiş şəkildə, bütün flaq üçün eyni qaydada edir.
Bunu belə təsəvvür etmək olar:
flowchart LR
A["os.Args (xam []string)"] --> B["flag.Parse()"]
B --> C["flaq dəyərləri: string/int/bool"]
B --> D["mövqe arqumentləri: flag.Args()"]
2. Flaqaların elan edilməsi
flag API-sini ilk dəfə görəndə o qəribə görünür: niyə flag.String flaqı oxumur, əksinə elan edir? Niyə göstərici qaytarır? Və niyə sadəcə bir komanda ilə “mənim üçün flaqaları oxu” yazmaq olmur? Bunların hamısı flag modelinin xüsusiyyətləridir: əvvəlcə proqramın hansı flaqaları dəstəklədiyini təsvir edirik, sonra təhlili çağırırıq, yalnız bundan sonra nəticələri oxuyuruq.
flag.String, flag.Int, flag.Bool
Flaqalar bu ailənin funksiyaları ilə elan edilir:
- flag.String(name, defaultValue, usage)
- flag.Int(name, defaultValue, usage)
- flag.Bool(name, defaultValue, usage)
Onların hər biri dəyərə göstərici qaytarır (*string, *int, *bool). Niyə belədir — bir az aşağıda baxarıq, amma hələlik praktik qaydanı yadda saxlayaq: Parse()-dan sonra dəyəri * vasitəsilə oxuyuruq.
Gözlər üçün daha rahat olsun deyə kiçik cədvəl:
| Funksiya | Dəyərin tipi | Nə qaytarır |
|---|---|---|
|
|
|
|
|
|
|
|
|
Kiçik nümunə: tədris məqsədli “psixədo-todo” üçün üç flaq elan edək (hələ heç nə saxlamır — sadəcə nə edəcəyimizi çap edirik).
package main
import (
"flag"
"fmt"
)
func main() {
title := flag.String("title", "", "task title")
priority := flag.Int("priority", 1, "priority 1..5")
done := flag.Bool("done", false, "mark task as done")
flag.Parse()
fmt.Println(*title, *priority, *done)
// (çıxış nümunəsi işə salınan arqumentlərdən asılıdır)
}
Diqqət edin: biz os.Args-a ümumiyyətlə əl vurmadıq. Məhz bu, xaos üzərində ilk “qələbə”dir.
Niyə *T qaytarılır
Başlayan biri *title görəndə tez-tez belə düşünür: “Mən sadəcə bir sətri oxumağı xahiş etmişdim, bəs niyə mənə... sətrin ünvanı verildi?”. Sual normaldır. Cavab budur ki, flag əvvəlcə dəyər üçün “konteyner” yaradır, sonra Parse() zamanı arqumentlərin təhlilinin nəticəsini ora yazır.
Bunu iş masasındakı kart kimi təsəvvür etmək olar:
- title — real dəyərin harada olduğunu göstərən kartdır
- *title — həmin ünvanda saxlanılan dəyərin özüdür
Yəni flag.String(...) sətri yox, sonradan sətirin qoyulacağı “yer”i qaytarır. Və flag.Parse()-dan sonra həmin yer ya istifadəçinin daxil etdiyi dəyərlə, ya da defolt qiymətlə dolmuş olur.
Daha sakit başa düşmək üçün tipləri çap etdiyimiz kiçik nümayiş:
package main
import (
"flag"
"fmt"
)
func main() {
title := flag.String("title", "untitled", "task title")
fmt.Printf("%T\n", title) // *string
flag.Parse()
fmt.Printf("%T\n", *title) // string
}
Burada göstəricilər mövzusuna dərindən girmirik (bu, ayrıca böyük hekayədir), amma bir vərdişə öyrəşmək vacibdir: flaq dəyəri həmişə * vasitəsilə oxunur.
3. Arqumentlərin təhlili: flag.Parse()
flag-də demək olar ritual kimi bir iş qaydası var: “elan et → parse et → oxu”. Və əgər bu ardıcıllıq pozularsa, proqram sanki batareyasız zəngli saat kimi işləyəcək: tanış bir şeydir, amma xeyri azdır.
flag.Parse() eyni anda bir neçə işi görür. O, işə salınan arqumentləri götürür, onların içində tanınmış flaqaları ayırır, dəyərləri lazım olan tiplərə çevirir (məsələn, "10" sətrini int(10)-a, flag.Int üçün), və nəticəni flaqaları elan edərkən aldığınız həmin göstərici dəyişənlərə yazır. Flaqalara aid olmayan hər şeyi isə “qalıq” kimi saxlayır — bu da mövqe arqumentləridir.
Parse()-ı unutduqda nə baş verdiyini göstərən sadə nümunə. (Bu, tipik səhvdir, ona görə bir dəfə görmək faydalıdır.)
package main
import (
"flag"
"fmt"
)
func main() {
limit := flag.Int("limit", 10, "max items")
fmt.Println(*limit) // 10 (həmişə 10, Parse etməyənə qədər)
}
Doğru variant isə budur:
package main
import (
"flag"
"fmt"
)
func main() {
limit := flag.Int("limit", 10, "max items")
flag.Parse()
fmt.Println(*limit) // -limit-dən asılıdır ...
}
Vacib nüans: flag dəyər ötürməyin hər iki populyar sintaksisini dəstəkləyir: -limit=5 və -limit 5. Sizə iki parser yazmağa və istifadəçinin hansı variantı seçdiyini təxmin etməyə ehtiyac yoxdur.
4. Mövqe arqumentləri
Flaqalar adlı ayarlardır (“bunu yandır”, “bu cür limit qoy”). Amma bir çox komandada əlavə olaraq mövqe arqumentləri də olur: fayl, yol, obyekt adı, tapşırıq mətni. Onlar adsız gəlir və sıralarına görə müəyyən edilir.
flag.Parse()-dan sonra mövqe arqumentlərini üç üsulla ala bilərsiniz:
- flag.Args() bütün mövqe arqumentləri ilə []string qaytarır
- flag.NArg() mövqe arqumentlərinin sayını qaytarır
- flag.Arg(i) i-ci mövqe arqumentini qaytarır (sıfırdan başlayır)
Gəlin “todo” komandamızı bir az daha canlı edək: qoy tapşırığın başlığı mövqe arqumenti olsun, prioritet isə flaq olsun. Yəni işə salma təxminən belə görünür:
- todo -priority 3 "Buy milk"
Kod:
package main
import (
"flag"
"fmt"
)
func main() {
priority := flag.Int("priority", 1, "priority 1..5")
flag.Parse()
fmt.Println("args:", flag.Args()) // args: [...]
fmt.Println("priority:", *priority) // priority: ...
}
flag.Arg(0)-dan əvvəl flag.NArg()-ı yoxlayın
İndi “başlıq”ı ilk mövqe arqumenti kimi oxuyaq, amma diqqətlə: əvvəlcə ümumiyyətlə onun olub-olmadığını yoxlayaq.
package main
import (
"flag"
"fmt"
)
func main() {
flag.Parse()
if flag.NArg() < 1 {
fmt.Println("TITLE missing")
return
}
fmt.Println("title:", flag.Arg(0))
}
Burada çox vacib vərdiş var: gözüyumulu oxumamaq flag.Arg(0)-ı. Əgər arqument yoxdursa, boş sətir alacaqsınız və proqram sanki işləyirmiş kimi davranıb mənasız şeylər edə bilər. Bu, açıq səhvdən də pisdir, çünki belə bug-lar uzun yaşayır və sizin özünəinamla qidalanır.
5. CLI-nin faydalı nüansları
Bulean flaqalar: flag.Bool
Bulean flaqalar CLI-nin ayrıca bir sevincidir. Sətirlərdən və rəqəmlərdən fərqli olaraq, onlar çox vaxt “rejim keçidi” kimi istifadə olunur: nəyisə yandırmaq/söndürmək. Yaxşı CLI isə adətən onları qısa yazmağa imkan verir.
flag.Bool-də praktik UX məqamı var: flaqı -done=true kimi vermək olar, amma adətən sadəcə -done də kifayət edir və bu, true deməkdir. Bu, komandaları daha qısa və rahat edir.
Qoy bizim “todo”da -done flaqı olsun və o, tapşırığı tamamlanmış kimi işarələsin (bəli, tapşırığı elə başdan tamamlanmış yaratmaq qəribədir — amma CLI də həyat kimi həmişə məntiqli olmur).
package main
import (
"flag"
"fmt"
)
func main() {
done := flag.Bool("done", false, "mark task as done")
flag.Parse()
fmt.Println("done:", *done) // done: true/false
}
Əgər istifadəçi -done verməsə, dəyər defolt olaraq qalacaq (false). Əgər versə, true olacaq. Və vacib olan odur ki, siz "true"/"false" sətrini əl ilə parse etməli və "yes" ilə nə edəcəyinizi düşünməli deyilsiniz.
-- terminatoru: “bundan sonra bunlar flaq deyil”
Bir balaca CLI problemi var ki, qəfil üzə çıxır. Təsəvvür edin, mövqe arqumentinin özü - ilə başlayır. Məsələn, tapşırıq mətni kimi -buy milk göndərmək istəyirsiniz (bəlkə belə üslubdur) və ya adı -data.txt olan fayl (hə, olur belə).
Əlavə qayda olmadan flag bunu flaq kimi şərh etməyə çalışacaq. “Bundan sonra flaq yoxdur, bundan sonra sadəcə arqumentlər var” demək üçün terminator -- istifadə olunur.
İstifadəçi baxımından komanda belə görünür:
- todo -priority 2 -- -buy milk
Kod isə dəyişmir: siz yenə flag.Parse() çağırırsınız, sonra da flag.Args() oxuyursunuz. Sadəcə -- parserə hissələri düzgün ayırmağa kömək edir.
---dən sonra hər şeyin Args-a düşdüyünü göstərən kiçik nümunə:
package main
import (
"flag"
"fmt"
)
func main() {
flag.Parse()
fmt.Println(flag.Args()) // ---dən sonra olan hər şey burada olacaq
}
Mini nümunə: “işə salma konfiqurasiyası”nı yığırıq
İndi kiçik bir bütöv hissə yığacağıq: flaqalar + mövqe arqumenti və proqramın nəyi anladığını çıxaracağıq. Bu hələ “həqiqi todo tətbiqi” deyil, amma artıq işə salma interfeysinin düzgün qatıdır.
Şərtlər sadədir: priority və done — flaqalar, TITLE — mövqe arqumenti.
package main
import (
"flag"
"fmt"
)
func main() {
priority := flag.Int("priority", 1, "priority 1..5")
done := flag.Bool("done", false, "mark as done")
flag.Parse()
title := ""
if flag.NArg() >= 1 {
title = flag.Arg(0)
}
fmt.Println("title:", title, "priority:", *priority, "done:", *done)
}
Bəli, burada hələ prioritet aralığını yoxlamırıq və usage-i gözəl çap etmirik — bunlar ayrıca bacarıqlardır. Bu mərhələdə vacib olan mexanikanı avtomatlaşdırmaqdır: elan et → Parse → oxu → Args-a bax.
6. Tipik səhvlər
Səhv №1: flag.Parse() çağırmağı unudurlar və “flaqalar işləmir” deyə təəccüblənirlər.
Bu ən çox rast gəlinən hekayədir. Proqram başlayır, heç nə çökmür, amma -priority 5 sanki nəzərə alınmır. Səbəb sadədir: Parse() çağırılmayana qədər dəyişənlərdə yalnız defolt dəyərlər olur.
Səhv №2: title əvəzinə *title istifadə etməyə çalışırlar və qəribə kompilyasiya səhvləri alırlar.
flag.String string yox, *string qaytarır. Bu ona görə edilir ki, Parse() nəticəni əvvəlcədən hazırlanmış yerə yaza bilsin. Həll sadədir: Parse()-dan sonra həmişə *title, *limit, *done ilə dereferens edin.
Səhv №3: flaqaları flag.Parse()-dan sonra elan edirlər və onların tanınacağını gözləyirlər.
Parse() arqumentləri artıq elan olunmuş flaqalar siyahısına əsasən təhlil edir. Əgər siz flaqı sonra elan etmisinizsə, Parse() onu “tanımayacaq”, çünki təhlil artıq baş verib. Qayda çox sərtdir: əvvəl elanlar, sonra Parse().
Səhv №4: mövqe arqumentlərini Parse()-dan əvvəl oxuyurlar və “boş/yanlış” nəticə alırlar.
Parse()-dan əvvəl flag paketi hələ flaqaları mövqe arqumentlərindən ayırmayıb. Buna görə flag.Args() təhlildən əvvəl ya boş olacaq, ya da gözlənilməz şeylər saxlayacaq. Düzgün ardıcıllıq: əvvəl flag.Parse(), yalnız sonra flag.Args()/flag.NArg()/flag.Arg(i).
Səhv №5: flag.NArg()-ı yoxlamadan flag.Arg(0) oxuyurlar və “səssiz” yanlış məntiq alırlar.
os.Args[i] oxunmasından fərqli olaraq, burada adətən panic almayacaqsınız, amma boş sətir alacaqsınız və sonra proqram boş adlı tapşırıq yaradıb elə biləcək ki, guya bu qəsdən olub. Mövqe arqumentlərini oxumazdan əvvəl sayını yoxlamaq faydalıdır, hətta “iki sətrə qənaət etmək” çox cazibədar görünsə də.
GO TO FULL VERSION