دروس الوحدة (4/5)
مشروع صغير: CLI مع الأعلام
دعونا نجمع العناصر الأساسية للوحدات السابقة معًا لبناء CLI اصطلاحي صغير: تحليل العلم باستخدام الحزمة flag، ومعالجة الوسائط الموضعية، والأخطاء في os.Stderr، وكود الخروج الصحيح.
الحزمة flag
package main
import (
"flag"
"fmt"
"os"
)
func main() {
n := flag.Int("n", 1, "number of repetitions")
upper := flag.Bool("u", false, "print in uppercase")
flag.Parse()
args := flag.Args() // remaining positional arguments
if len(args) < 1 {
fmt.Fprintln(os.Stderr, "usage: echo [-n N] [-u] <text>")
os.Exit(2)
}
text := args[0]
if *upper {
text = strings.ToUpper(text)
}
for i := 0; i < *n; i++ {
fmt.Println(text)
}
}النقاط الرئيسية:
flag.Int،flag.String،flag.Bool،flag.Duration(إلخ.) تُرجع مؤشر: تستخدم*n، *upper.- يجب استدعاء
flag.Parse()مرة واحدة، قبل قراءة القيم وقبلflag.Args(). flag.Args()تُرجع الوسائط الموضعية (تلك التي تأتي بعد--أو بعد العلامة الأخيرة).- النماذج المقبولة:
-n 3،-n=3،--n=3. القيم المنطقية:-u(صحيح)،-u=false.
رموز الخروج التقليدية
os.Exit(0) // success
os.Exit(1) // generic error
os.Exit(2) // usage error (incorrect flag usage)تتوقع الأصداف والبرامج النصية هذه الرموز: استخدمها باستمرار. مطلقًا الخروج من نظام goroutine غير رئيسي باستخدام os.Exit: فهو يتخطى كل defer، بما في ذلك log التنظيف.
Stderr vs Stdout
قاعدة يونكس القديمة والمقدسة:
os.Stdout→ مخرجات مفيدة، قابلة للتوجيه (cli | grep ...).os.Stderr→ التشخيص والأخطاء وأشرطة التقدم والاستخدام.
fmt.Println("result: 42") // stdout
fmt.Fprintln(os.Stderr, "error: ...") // stderrبهذه الطريقة، يعرض cli 2>/dev/null النتائج فقط، ويعيد cli >out.txt توجيه النتيجة فقط وتظل الأخطاء مرئية.
log مقابل fmt
fmtلإخراج المستخدم.logللتشخيصات: يضيف طابعًا زمنيًا، ويكتب إلىos.Stderrافتراضيًا، ويحتوي علىlog.Fatal(السجلات +os.Exit(1)) وlog.Panic.
log.SetFlags(log.LstdFlags | log.Lshortfile) // timestamp + file:line
log.Fatal("boom") // prints and exits with 1بالنسبة للتطبيقات الحديثة (Go 1.21+) استخدم log/slog للتسجيل المنظم (JSON أو النص، والمستويات، والحقول المكتوبة).
البنية: منفصلة main عن المنطق
func main() {
if err := run(os.Args[1:], os.Stdout, os.Stderr); err != nil {
fmt.Fprintln(os.Stderr, err)
os.Exit(1)
}
}
func run(args []string, stdout, stderr io.Writer) error {
// here use a new flag.FlagSet instead of the global flag.CommandLine
fs := flag.NewFlagSet("echo", flag.ContinueOnError)
fs.SetOutput(stderr)
n := fs.Int("n", 1, "repetitions")
if err := fs.Parse(args); err != nil {
return err
}
// ...
return nil
}المزايا:
- قابل للاختبار: تمرر
bytes.Bufferكـ stdout/stderr وتتحقق من الإخراج. - لا توجد حالة عامة: كل
FlagSetمعزول. mainيظل صغيرًا: فهو يقوم بإعداد IO + المفوضين.
أوامر فرعية
بالنسبة إلى واجهة سطر الأوامر ذات الأوامر الفرعية (mycli serve، mycli migrate)، وهو مخطط لا يحتوي على تبعيات خارجية:
func main() {
if len(os.Args) < 2 {
usage()
os.Exit(2)
}
switch os.Args[1] {
case "serve":
serveCmd(os.Args[2:])
case "migrate":
migrateCmd(os.Args[2:])
default:
usage()
os.Exit(2)
}
}كل أمر فرعي له flag.NewFlagSet خاص به. بالنسبة للمشروعات الأكبر حجمًا، تقوم المكتبات مثل cobra أو urfave/cli ببناء هذا المخطط بمساعدة يتم إنشاؤها تلقائيًا.
تمارين
قم بتعريف العلامة -n من النوع int مع القيمة الافتراضية 1 ووصف "التكرار"، ثم قم بتمييز flag.Parse() وختم القيمة.
الحل متاح بعد 3 من المحاولات
إذا لم يكن هناك وسيط موضعي بعد العلم.تحليل، فقم بطباعة "manca argomento" على Stderr وإنهاء مع os.Exit(1).
الحل متاح بعد 3 من المحاولات
ما نوعية تدفق رسائل الأخطاء واستخدامها في واجهة سطر الأوامر (CLI) الملائمة لنظام التشغيل Unix؟
fmt.Fprintln(???, "uso: prog ...")