os.Args
os.Args הוא slice של מחרוזות. os.Args[0] הוא שם התוכנית, והשאר הם הארגומנטים בדיוק כפי שה-shell העביר אותם.
בחלונית Args של העורך, כל שדה הוא ארגומנט אחד שמועבר כמו שהוא. נסו hello, two words ו--v בשלושה שדות: התוכנית רואה שלושה ארגומנטים, ו-two words נשאר ארגומנט אחד עם רווח בתוכו. בטרמינל ה-shell הוא שמפצל, כך שאותו דבר נכתב go run . hello "two words" -v.
בדקו תמיד את len(os.Args) לפני גישה לאינדקס. os.Args[1] בלי ארגומנטים גורם ל-panic עם index out of range [1] with length 1.
ארגומנטים הם מחרוזות. המירו מספרים עם strconv.Atoi או strconv.ParseFloat וטפלו בשגיאה, כי משתמשים מקלידים כל דבר.
חבילת flag
לאפשרויות כמו -port 8080 -verbose השתמשו ב-flag. היא מנתחת, ממירה טיפוסים, מדווחת על שגיאות ומייצרת הודעת עזרה.
בלי ארגומנטים התוכנית מדפיסה hello, world פעם אחת. בחלונית Args, נסו -name ו-Gopher בשני שדות, ואחר כך -count=3, -loud ו-extra בשלושה שדות נוספים. התוכנית מדפיסה HELLO, GOPHER! שלוש פעמים ואת remaining args: [extra].
איך flags עובדים:
- כל פונקציית הגדרה (
flag.String,flag.Int,flag.Bool,flag.Float64,flag.Duration,flag.Uint64...) מקבלת שם, ערך ברירת מחדל ומחרוזת שימוש, ומחזירה מצביע. קראו את הערך עם*nameאחריflag.Parse(). - צורות ה-
Varנקשרות למשתנה שכבר יש לכם:flag.IntVar(&cfg.Port, "port", 8080, "port"). זה מסודר יותר עבור struct של קונפיגורציה. - משתמשים יכולים לכתוב
-name value,-name=value,--name valueאו--name=value. Go לא מבחינה בין מקף אחד לשניים. - ערכים בוליאניים צריכים
=כדי לקבל ערך.-loudמציב true,-loud=falseמציב false, אבל-loud falseמציב true ומשאיר אתfalseכארגומנט פוזיציוני. - הניתוח נעצר בארגומנט הראשון שאינו flag (או ב-
--).prog file.txt -vמתייחס ל--vכארגומנט פוזיציוני. שימו את ה-flags קודם. flag.Args()מחזירה את הארגומנטים הפוזיציוניים שנשארו,flag.NArg()את מספרם, ו-flag.Arg(i)אחד מהם.
flag לא מוכר או ערך שגוי מדפיסים שגיאה ואת הוראות השימוש, ויוצאים עם סטטוס 2. -h או -help מדפיסים את הוראות השימוש ויוצאים עם סטטוס 0 (החל מ-Go 1.15). טקסט השימוש נוצר מההגדרות שלכם:
Usage of greet:
-count int
how many times (default 1)
-delay duration
pause between greetings, e.g. 10ms
-loud
shout the greeting
-name string
who to greet (default "world")
הציבו פונקציה ב-flag.Usage כדי להדפיס כותרת משלכם לפני הקריאה ל-flag.PrintDefaults().
תת-פקודות עם FlagSet
לכלים כמו git commit -m msg יש תת-פקודות עם flags משלהן. צרו flag.FlagSet לכל תת-פקודה ובחרו ביניהן עם switch על הארגומנט הראשון:
flag.ContinueOnError גורם ל-Parse להחזיר שגיאה במקום לצאת, וכך הפונקציה נשארת ניתנת לבדיקה. run מקבלת את הארגומנטים כפרמטר במקום לקרוא את os.Args, כך שבדיקה יכולה לקרוא ישירות ל-run([]string{"list", "-all"}). נסו list ו--all בחלונית Args, או delete כדי לראות את מסלול השגיאה.
עבור CLIs גדולים עם פקודות מקוננות, השלמה אוטומטית ב-shell ותיעוד שנוצר אוטומטית, רוב הפרויקטים משתמשים בחבילה החיצונית github.com/spf13/cobra. חבילת flag הסטנדרטית מכסה היטב כלים קטנים.
משתני סביבה
os.Getenvמחזירה""גם כשהמשתנה חסר וגם כשהוא מוגדר כמחרוזת ריקה.os.LookupEnvמבחינה ביניהם.- הערכים הם תמיד מחרוזות. המירו ובדקו אותם בזמן העלייה, ונכשלו עם הודעה ברורה ולא באמצע טיפול בבקשה.
os.Setenvמשפיעה על התהליך הנוכחי ועל תהליכי בן שמופעלים אחר כך. היא לא יכולה לשנות את הסביבה של ה-shell שהפעיל אתכם.os.Environ()מחזירה את כל המשתנים כמחרוזות"KEY=value".
מבנה נפוץ לקונפיגורציה: flags לדברים שאדם מקליד, משתני סביבה להגדרות פריסה (פלטפורמות קונטיינרים מגדירות אותם), כש-flags גוברים על הסביבה והסביבה גוברת על ברירות המחדל.
קודי יציאה ו-os.Exit
תוכנית Go יוצאת עם סטטוס 0 כש-main חוזרת. os.Exit(code) מסיימת את התהליך מיד עם הסטטוס הזה. לפי המוסכמה, 0 הוא הצלחה, 1 הוא שגיאה כללית, ו-2 הוא שגיאת שימוש (חבילת flag משתמשת ב-2).
os.Exit לא מריצה פונקציות defer. קבצים לא נכתבים עד הסוף וניקוי של defer מדולג. log.Fatal קוראת ל-os.Exit(1) ויש לה אותה השפעה. השאירו את os.Exit במקום אחד, בסוף main, ושימו את התוכנית האמיתית בפונקציה run שמחזירה שגיאה, כמו בדוגמת תת-הפקודות:
func main() {
if err := run(os.Args[1:]); err != nil {
fmt.Fprintln(os.Stderr, "error:", err)
os.Exit(1)
}
}
כתבו הודעות שגיאה ל-os.Stderr, לא ל-os.Stdout, כדי שיישארו גלויות כשהפלט מופנה לקובץ או מועבר ב-pipe לפקודה אחרת. panic שלא טופל ב-recover יוצא עם סטטוס 2.
טעויות נפוצות
- גישה לאינדקס של
os.Argsבלי לבדוק את האורך. ארגומנטים חסרים גורמים ל-panic. - קריאת flags לפני
flag.Parse(). מקבלים את ברירות המחדל. - שוכחים את ה-
*.fmt.Println(port)מדפיס כתובת כמו0xc000012345, לא את הערך. - שמים flags אחרי ארגומנטים פוזיציוניים.
prog input.txt -vלא מנתח את-v. -verbose falseעבור flag בוליאני. כתבו-verbose=false.- קוראים ל-
os.Exitאו ל-log.Fatalעמוק בתוך התוכנית. ניקוי ה-defer לא רץ אף פעם ואי אפשר לבדוק את הקוד. החזירו שגיאות למעלה עדmain.
שאלות נפוצות
איך מקבלים ארגומנטים משורת הפקודה ב-Go?
os.Args הוא []string שמחזיק את שם התוכנית באינדקס 0 ואת הארגומנטים אחריו. os.Args[1:] הם הארגומנטים שהמשתמש הקליד. בדקו את len(os.Args) לפני גישה לאינדקס, אחרת התוכנית תיכנס ל-panic כשחסר ארגומנט.
איך משתמשים בחבילת flag ב-Go?
מצהירים על flags, קוראים ל-flag.Parse() ואז קוראים את הערכים: port := flag.Int("port", 8080, "port to listen on"), flag.Parse(), fmt.Println(*port). הפונקציות מחזירות מצביעים. המשתמשים כותבים -port=9000, -port 9000 או --port 9000, ו--h מדפיס את הוראות השימוש שנוצרו אוטומטית.
איך קוראים משתנה סביבה ב-Go?
os.Getenv("HOME") מחזירה את הערך, או מחרוזת ריקה אם המשתנה לא מוגדר. כדי להבחין בין משתנה לא מוגדר למשתנה שמוגדר כריק, השתמשו ב-v, ok := os.LookupEnv("HOME"). os.Setenv משנה את הסביבה של התהליך הנוכחי ושל תהליכי הבן שהוא מפעיל אחר כך.
האם os.Exit מריצה פונקציות defer ב-Go?
לא. os.Exit מסיימת את התהליך מיד עם קוד הסטטוס שניתן, וקריאות defer לא רצות, כך שפלט שנמצא ב-buffer עלול ללכת לאיבוד וקבצים עלולים לא להיכתב עד הסוף. דפוס נפוץ הוא func main() { if err := run(); err != nil { fmt.Fprintln(os.Stderr, err); os.Exit(1) } }, כשכל העבודה האמיתית וכל ה-defers נמצאים בתוך run.