الانتقال إلى المحتوى الرئيسي
الوحدة 10 · الدرس 4 من 549/50 في الدورة~18 min
دروس الوحدة (4/5)

مشروع صغير: CLI مع الأعلام

دعونا نجمع العناصر الأساسية للوحدات السابقة معًا لبناء CLI اصطلاحي صغير: تحليل العلم باستخدام الحزمة flag، ومعالجة الوسائط الموضعية، والأخطاء في os.Stderr، وكود الخروج الصحيح.

الحزمة flag

Go
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.

رموز الخروج التقليدية

Go
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 → التشخيص والأخطاء وأشرطة التقدم والاستخدام.
Go
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.
Go
log.SetFlags(log.LstdFlags | log.Lshortfile) // timestamp + file:line
log.Fatal("boom") // prints and exits with 1

بالنسبة للتطبيقات الحديثة (Go 1.21+) استخدم log/slog للتسجيل المنظم (JSON أو النص، والمستويات، والحقول المكتوبة).

البنية: منفصلة main عن المنطق

Go
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)، وهو مخطط لا يحتوي على تبعيات خارجية:

Go
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 ببناء هذا المخطط بمساعدة يتم إنشاؤها تلقائيًا.

تمارين

تمرين#go.m10.l4.e1
المحاولات: 0جارٍ التحميل…

قم بتعريف العلامة -n من النوع int مع القيمة الافتراضية 1 ووصف "التكرار"، ثم قم بتمييز flag.Parse() وختم القيمة.

جارٍ تحميل المحرر…

الحل متاح بعد 3 من المحاولات

تمرين#go.m10.l4.e2
المحاولات: 0جارٍ التحميل…

إذا لم يكن هناك وسيط موضعي بعد العلم.تحليل، فقم بطباعة "manca argomento" على Stderr وإنهاء مع os.Exit(1).

جارٍ تحميل المحرر…

الحل متاح بعد 3 من المحاولات

Quiz#go.m10.l4.e3
جاهز

ما نوعية تدفق رسائل الأخطاء واستخدامها في واجهة سطر الأوامر (CLI) الملائمة لنظام التشغيل Unix؟

Go
fmt.Fprintln(???, "uso: prog ...")
خيارات الإجابة