Go: التطبيق العملي: تطوير أدوات واجهة سطر الأوامر

آخر تحديث: 2026-08-26

يُعد تطوير أدوات واجهة سطر الأوامر (CLI) إحدى نقاط قوة لغة Go — فهي تُترجم إلى ملف ثنائي واحد، وتعمل عبر الأنظمة المختلفة، ولا تحتوي على أي تبعيات. وقد أصبح كتابة أدوات واجهة سطر الأوامر بلغة Go هو المعيار السائد في عصر التطبيقات السحابية الأصلية.

بدءًا من docker مرورًا بـ kubectl وصولاً إلى gh، فإن جميع أدوات واجهة الأوامر (CLI) السائدة تقريبًا مكتوبة بلغة Go. في هذا الدرس، ستتقن جميع المهارات الأساسية لتطوير واجهة الأوامر (CLI) وستقوم بإنشاء برنامج كامل لإدارة كلمات المرور.

1. ستتعلم



2. قصة حقيقية لمهندس عمليات

(1) المشكلة: الاضطرار إلى إنشاء كلمة مرور يدويًّا في كل مرة

بوب هو مهندس عمليات يحتاج في كثير من الأحيان إلى إنشاء كلمات مرور معقدة لمختلف الخدمات:

«اليوم أقوم بإنشاء كلمة مرور لـ PostgreSQL، وغدًا لـ Redis، وبعد غد لحساب إداري. وفي كل مرة، أضطر إلى فتح موقع ويب لإنشاء كلمات المرور، ثم نسخها ولصقها في 1Password — خمس مرات في اليوم. هذا أمر مزعج للغاية. علاوة على ذلك، تختلف متطلبات المواقع الإلكترونية من موقع لآخر: 12 حرفًا مع أحرف خاصة، أو 16 حرفًا بدون أحرف غامضة...»

يبحث بوب عن أداة CLI مثل هذه:

BASH
# Desired CLI
$ passgen generate --length 16 --special
Generated: Kd9#mP2$xL7qR!vB

$ passgen save mydb -p Kd9#mP2$xL7qR!vB
Saved: mydb

$ passgen list
mydb    (created: 2026-07-08)
admin   (created: 2026-07-05)
api-key (created: 2026-07-01)

$ passgen get mydb
mydb: Kd9#mP2$xL7qR!vB

لكن لم تكن هناك أي أدوات متوفرة في السوق تلبي احتياجاته بالكامل. لذا قرر أن يطور واحدة بنفسه باستخدام لغة Go.

(2) حل لعبة الغو: حزمة الأعلام + عمليات الإدخال/الإخراج للملفات

GO
// passgen.go — Version 1: Basic password generation
package main

import (
    "crypto/rand"
    "flag"
    "fmt"
    "math/big"
)

var (
    length  = flag.Int("length", 12, "password length")
    special = flag.Bool("special", false, "include special characters")
    count   = flag.Int("count", 1, "number of passwords to generate")
)

const (
    lowerChars   = "abcdefghijklmnopqrstuvwxyz"
    upperChars   = "ABCDEFGHIJKLMNOPQRSTUVWXYZ"
    digitChars   = "0123456789"
    specialChars = "!@#$%^&*()-_=+[]{}|;:,.<>?"
)

func generatePassword(length int, includeSpecial bool) string {
    charset := lowerChars + upperChars + digitChars
    if includeSpecial {
        charset += specialChars
    }

    pwd := make([]byte, length)
    for i := range pwd {
        n, _ := rand.Int(rand.Reader, big.NewInt(int64(len(charset))))
        pwd[i] = charset[n.Int64()]
    }
    return string(pwd)
}

func main() {
    flag.Parse()

    for i := 0; i < *count; i++ {
        fmt.Println(generatePassword(*length, *special))
    }
}
BASH
$ go run passgen.go --length 16 --special
Kd9#mP2$xL7qR!vB

$ go run passgen.go --length 8
aB3xK9mP

(3) الأداء: الوضع اليدوي مقابل واجهة سطر الأوامر (CLI)

الطريقة السرعة قابلية التكرار التكامل مع CI/CD الأمان
إنشاء المواقع الإلكترونية 30 ثانية/مرة ❌ (خطر على الخصوصية)
إنشاء يدويًا 60 ثانية لكل مثيل ✅ يمكن تكراره
Go CLI 0.5 ثانية لكل عملية ✅ قابلة للبرمجة ✅ قابلة للتكامل ✅ تعمل محليًّا
💡 نصيحة: يبلغ حجم الملف الثنائي لـ Go حوالي 10 ميغابايت (بدون بيئة التشغيل)، وبعد تشغيل go build، يمكنك ببساطة نسخه إلى أي خادم يعمل بنظام Linux أو Mac أو Windows لتشغيله. وهذه هي الميزة الأساسية لاستخدام Go في تطبيقات واجهة الأوامر (CLI).



3. حزمة flag: تحليل معلمات سطر الأوامر

(1) أساسيات الأعلام

GO
package main

import (
    "flag"
    "fmt"
)

func main() {
    // Register parameters (returns pointers)
    name := flag.String("name", "World", "name to greet")
    age := flag.Int("age", 0, "age")
    verbose := flag.Bool("verbose", false, "verbose output")

    // Parse (must be called before reading values)
    flag.Parse()

    if *verbose {
        fmt.Printf("name=%s, age=%d\n", *name, *age)
    }
    fmt.Printf("Hello, %s!\n", *name)
}
BASH
$ go run main.go --name Alice --age 28 --verbose
name=Alice, age=28
Hello, Alice!

$ go run main.go
Hello, World!

(2) مرجع سريع لنوع flag

الدالة النوع مثال
flag.String(name, default, usage) *string --name Alice
flag.Int(name, default, usage) *int --port 8080
flag.Float64(name, default, usage) *float64 --ratio 0.5
flag.Bool(name, default, usage) *bool --verbose
flag.Duration(name, default, usage) *time.Duration --timeout 5s

▶ مثال: معلمة قصيرة مع علامة + معلمة بدون علامة

GO
package main

import (
    "flag"
    "fmt"
    "os"
)

func main() {
    // Custom Usage message
    flag.Usage = func() {
        fmt.Fprintf(os.Stderr, "Usage: mytool [options] <file path>\n\n")
        fmt.Fprintf(os.Stderr, "Options:\n")
        flag.PrintDefaults()
    }

    // Register parameters
    output := flag.String("o", "output.txt", "output file path")
    limit := flag.Int("n", 0, "lines to process (0=all)")
    debug := flag.Bool("debug", false, "enable debug mode")

    // Parse
    flag.Parse()

    // Access non-flag arguments (remaining args)
    args := flag.Args()
    if len(args) < 1 {
        flag.Usage()
        os.Exit(1)
    }

    filePath := args[0]
    fmt.Printf("Processing file: %s\n", filePath)
    fmt.Printf("Output: %s\n", *output)
    fmt.Printf("Lines: %d\n", *limit)
    fmt.Printf("Debug: %v\n", *debug)

    // Supports both -o and --o forms
    // Supports both -o=file and -o file syntax
}
▶ جرّب الكود
BASH
$ go run main.go -o result.txt -n 100 data.csv
Processing file: data.csv
Output: result.txt
Lines: 100
Debug: false


4. os.Args و os.Stdin/Stdout

(1) os.Args: المعلمات الأولية

GO
package main

import (
    "fmt"
    "os"
)

func main() {
    fmt.Printf("Program: %s\n", os.Args[0])
    fmt.Printf("Arg count: %d\n", len(os.Args)-1)

    for i, arg := range os.Args[1:] {
        fmt.Printf("Arg %d: %s\n", i+1, arg)
    }
}
BASH
$ go run main.go hello world --debug
Program: /tmp/main
Arg count: 3
Arg 1: hello
Arg 2: world
Arg 3: --debug

▶ مثال: ربط المتغير flag (IntVar / StringVar)

GO
package main

import (
    "flag"
    "fmt"
)

func main() {
    // flag.Int returns a pointer vs flag.IntVar binds to an existing variable
    var (
        name   string
        port   int
        debug  bool
    )

    flag.StringVar(&name, "name", "World", "name")
    flag.IntVar(&port, "port", 8080, "port")
    flag.BoolVar(&debug, "debug", false, "debug mode")

    flag.Parse()

    fmt.Printf("name=%s, port=%d, debug=%v\n", name, port, debug)
}
▶ جرّب الكود
💡 نصيحة: flag.IntVar(&dest, ...) يرتبط بمتغير موجود، بينما flag.Int(...) يُرجع مؤشرًا. كلاهما متكافئان تمامًا — اختر الأسلوب الذي تفضله. يُعد IntVar أكثر ملاءمةً لإعلان المتغيرات في موقع مركزي.

(3) os.Stdin: مدخلات الأنبوب

GO
package main

import (
    "bufio"
    "fmt"
    "os"
    "strings"
)

func main() {
    stat, _ := os.Stdin.Stat()
    hasStdin := (stat.Mode() & os.ModeCharDevice) == 0

    if hasStdin {
        // Read from pipe (echo "hello" | go run main.go)
        scanner := bufio.NewScanner(os.Stdin)
        for scanner.Scan() {
            line := strings.ToUpper(scanner.Text())
            fmt.Println(line)
        }
    } else {
        // Read from arguments (go run main.go hello)
        if len(os.Args) > 1 {
            fmt.Println(strings.ToUpper(strings.Join(os.Args[1:], " ")))
        } else {
            fmt.Println("Please provide input (pipe or arguments)")
        }
    }
}
BASH
$ echo "hello world" | go run main.go
HELLO WORLD

$ go run main.go hello world
HELLO WORLD

▶ مثال: الإدخال التفاعلي (fmt.Scanf)

GO
package main

import "fmt"

func main() {
    var name string
    var age int

    fmt.Print("Enter name: ")
    fmt.Scanf("%s", &name)

    fmt.Print("Enter age: ")
    fmt.Scanf("%d", &age)

    fmt.Printf("Hello %s, you are %d years old\n", name, age)
}
▶ جرّب الكود

▶ مثال: إعادة توجيه os.Stdout

GO
package main

import (
    "fmt"
    "os"
)

func main() {
    // Write directly to stdout
    fmt.Fprintln(os.Stdout, "Standard output")
    fmt.Fprintln(os.Stderr, "Error output")

    // Redirect to file
    file, _ := os.Create("output.log")
    defer file.Close()

    // fmt.Fprintln can write to any io.Writer
    fmt.Fprintln(file, "This line was written to the file")
    fmt.Fprintln(file, "Second line")
}
▶ جرّب الكود

(6) مقارنة بين مصادر المدخلات

طريقة الإدخال مثال على الأمر حالة الاستخدام
معلمات سطر الأوامر mytool --name Alice خيارات التكوين، اسم الملف
أنبوب stdin cat data | mytool كميات كبيرة من البيانات، معالجة متسلسلة
الإدخال التفاعلي mytool ينتظر إدخال المستخدم إدخال يتطلب توجيهات
متغير بيئي MYTOOL_DEBUG=1 mytool إعدادات حساسة


5. مكتبة كوبرا: وضع الأوامر الفرعية

(1) مقدمة عن كوبرا

يُعد «كوبرا» إطار عمل واجهة الأوامر (CLI) الأكثر شيوعًا للغة «Go» (الذي كتبه مبتكر «كوبيرنيتس»)، وهو يدعم الأوامر الفرعية ورسائل المساعدة والإكمال التلقائي.

BASH
# Install cobra-cli
$ go install github.com/spf13/cobra-cli@latest

# Initialize project
$ cobra-cli init passgen
$ cobra-cli add generate
$ cobra-cli add save
$ cobra-cli add list

▶ مثال: بنية الأمر الفرعي «cobra»

GO
// cmd/root.go
package cmd

import (
    "fmt"
    "os"

    "github.com/spf13/cobra"
)

var rootCmd = &cobra.Command{
    Use:   "passgen",
    Short: "Password Manager",
    Long:  `A CLI tool for generating, storing, and managing passwords.`,
}

func Execute() {
    if err := rootCmd.Execute(); err != nil {
        fmt.Fprintln(os.Stderr, err)
        os.Exit(1)
    }
}
▶ جرّب الكود
GO
// cmd/generate.go
package cmd

import (
    "fmt"
    "crypto/rand"
    "math/big"

    "github.com/spf13/cobra"
)

var (
    genLength  int
    genSpecial bool
    genCount   int
)

var generateCmd = &cobra.Command{
    Use:   "generate",
    Short: "Generate a random password",
    Long:  `Generate a random password with specified length and complexity.`,
    Run: func(cmd *cobra.Command, args []string) {
        for i := 0; i < genCount; i++ {
            fmt.Println(generatePassword(genLength, genSpecial))
        }
    },
}

func init() {
    rootCmd.AddCommand(generateCmd)
    generateCmd.Flags().IntVarP(&genLength, "length", "l", 12, "password length")
    generateCmd.Flags().BoolVarP(&genSpecial, "special", "s", false, "include special characters")
    generateCmd.Flags().IntVarP(&genCount, "count", "c", 1, "number to generate")
}

func generatePassword(length int, special bool) string {
    charset := "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789"
    if special {
        charset += "!@#$%^&*()-_=+[]{}|;:,.<>?"
    }
    pwd := make([]byte, length)
    for i := range pwd {
        n, _ := rand.Int(rand.Reader, big.NewInt(int64(len(charset))))
        pwd[i] = charset[n.Int64()]
    }
    return string(pwd)
}
GO
// main.go
package main

import "passgen/cmd"

func main() {
    cmd.Execute()
}
BASH
$ go build -o passgen
$ ./passgen generate --length 16 --special
Kd9#mP2$xL7qR!vB
$ ./passgen generate -l 8
aB3xK9mP

(3) فلاغ مقابل كوبرا

البعد حزمة الإشارات كوبرا
مدمج ✅ المكتبة القياسية ❌ طرف ثالث
الأمر الفرعي ❌ غير مدعوم ✅ مدعوم أصلاً
المساعدة في مجال السيارات ✅ الأساسيات ✅ الجماليات
الإكمال التلقائي ✅ bash/zsh/fish
حالات الاستخدام البرامج النصية البسيطة الأدوات المعقدة متعددة الأوامر


6. مثال كامل: واجهة سطر الأوامر لبرنامج إدارة كلمات المرور

اجمع كل هذه المعرفة في أداة شاملة لإدارة كلمات المرور:

GO
// main.go
package main

import (
    "bufio"
    "crypto/rand"
    "encoding/json"
    "flag"
    "fmt"
    "math/big"
    "os"
    "strings"
    "time"
)

// ---------- Data Structures ----------

type PasswordEntry struct {
    Name      string    `json:"name"`
    Password  string    `json:"password"`
    CreatedAt time.Time `json:"created_at"`
    UpdatedAt time.Time `json:"updated_at,omitempty"`
}

type PasswordStore struct {
    entries  map[string]PasswordEntry
    filePath string
}

const defaultStoreFile = "passwords.json"

// ---------- Storage Layer ----------

func NewPasswordStore(filePath string) *PasswordStore {
    store := &PasswordStore{
        entries:  make(map[string]PasswordEntry),
        filePath: filePath,
    }
    store.load()
    return store
}

func (s *PasswordStore) load() {
    data, err := os.ReadFile(s.filePath)
    if err != nil {
        return
    }
    json.Unmarshal(data, &s.entries)
}

func (s *PasswordStore) save() error {
    data, err := json.MarshalIndent(s.entries, "", "  ")
    if err != nil {
        return err
    }
    return os.WriteFile(s.filePath, data, 0600)
}

func (s *PasswordStore) Add(name, password string) {
    now := time.Now()
    if existing, ok := s.entries[name]; ok {
        existing.Password = password
        existing.UpdatedAt = now
        s.entries[name] = existing
    } else {
        s.entries[name] = PasswordEntry{
            Name:      name,
            Password:  password,
            CreatedAt: now,
        }
    }
    s.save()
}

func (s *PasswordStore) Get(name string) (PasswordEntry, bool) {
    entry, ok := s.entries[name]
    return entry, ok
}

func (s *PasswordStore) Delete(name string) bool {
    _, ok := s.entries[name]
    if ok {
        delete(s.entries, name)
        s.save()
    }
    return ok
}

func (s *PasswordStore) List() []PasswordEntry {
    result := make([]PasswordEntry, 0, len(s.entries))
    for _, entry := range s.entries {
        result = append(result, entry)
    }
    return result
}

// ---------- Password Generation ----------

const (
    letters  = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ"
    digits   = "0123456789"
    specials = "!@#$%^&*()-_=+[]{}|;:,.<>?"
    noAmbigu = "ABCDEFGHJKLMNPQRSTUVWXYZabcdefghjkmnpqrstuvwxyz23456789"
)

func generatePassword(length int, useSpecial bool) (string, error) {
    if length < 4 {
        return "", fmt.Errorf("password length must be at least 4")
    }

    charset := letters + digits
    if useSpecial {
        charset += specials
    }

    pwd := make([]byte, length)
    for i := range pwd {
        n, err := rand.Int(rand.Reader, big.NewInt(int64(len(charset))))
        if err != nil {
            return "", err
        }
        pwd[i] = charset[n.Int64()]
    }
    return string(pwd), nil
}

// ---------- CLI Commands ----------

func cmdGenerate(args []string) {
    cmd := flag.NewFlagSet("generate", flag.ExitOnError)
    length := cmd.Int("length", 12, "password length")
    special := cmd.Bool("special", false, "include special characters")
    count := cmd.Int("count", 1, "number to generate")
    _ = cmd.Bool("no-ambiguous", false, "exclude ambiguous characters (0OIl1)")
    cmd.Parse(args)

    for i := 0; i < *count; i++ {
        pwd, err := generatePassword(*length, *special)
        if err != nil {
            fmt.Fprintf(os.Stderr, "Error: %v\n", err)
            os.Exit(1)
        }
        fmt.Println(pwd)
    }
}

func cmdSave(args []string, store *PasswordStore) {
    cmd := flag.NewFlagSet("save", flag.ExitOnError)
    password := cmd.String("password", "", "password (leave empty to auto-generate)")
    length := cmd.Int("length", 16, "auto-generated password length")
    cmd.Parse(args)

    name := cmd.Arg(0)
    if name == "" {
        fmt.Fprintln(os.Stderr, "Usage: passgen save <name> [--password <pwd>]")
        os.Exit(1)
    }

    pwd := *password
    if pwd == "" {
        var err error
        pwd, err = generatePassword(*length, true)
        if err != nil {
            fmt.Fprintf(os.Stderr, "Error: %v\n", err)
            os.Exit(1)
        }
    }

    store.Add(name, pwd)
    fmt.Printf("Saved: %s = %s\n", name, pwd)
}

func cmdGet(args []string, store *PasswordStore) {
    if len(args) < 1 {
        fmt.Fprintln(os.Stderr, "Usage: passgen get <name>")
        os.Exit(1)
    }

    entry, ok := store.Get(args[0])
    if !ok {
        fmt.Fprintf(os.Stderr, "Not found: %s\n", args[0])
        os.Exit(1)
    }
    fmt.Printf("%s: %s\n", entry.Name, entry.Password)
}

func cmdList(args []string, store *PasswordStore) {
    entries := store.List()
    if len(entries) == 0 {
        fmt.Println("(no saved passwords)")
        return
    }

    fmt.Printf("%-20s %-20s %s\n", "Name", "Password", "Created")
    fmt.Println(strings.Repeat("-", 60))
    for _, e := range entries {
        pwd := e.Password
        if len(pwd) > 16 {
            pwd = pwd[:16] + "..."
        }
        fmt.Printf("%-20s %-20s %s\n",
            e.Name, pwd, e.CreatedAt.Format("2006-01-02"))
    }
}

func cmdDelete(args []string, store *PasswordStore) {
    if len(args) < 1 {
        fmt.Fprintln(os.Stderr, "Usage: passgen delete <name>")
        os.Exit(1)
    }

    if store.Delete(args[0]) {
        fmt.Printf("Deleted: %s\n", args[0])
    } else {
        fmt.Fprintf(os.Stderr, "Not found: %s\n", args[0])
    }
}

func cmdExport(args []string, store *PasswordStore) {
    output := "passwords_export.json"
    if len(args) > 0 {
        output = args[0]
    }

    data, _ := json.MarshalIndent(store.List(), "", "  ")
    os.WriteFile(output, data, 0600)
    fmt.Printf("Exported to: %s\n", output)
}

// ---------- Main Function ----------

func printUsage() {
    fmt.Println(`Password Manager CLI

Usage:
  passgen <command> [options]

Commands:
  generate    Generate a random password
  save        Save a password
  get         Retrieve a password
  list        List all passwords
  delete      Delete a password
  export      Export passwords to a file

Options:
  --store     Specify storage file path (default: passwords.json)

Examples:
  passgen generate --length 16 --special
  passgen save mydb --length 20
  passgen get mydb
  passgen list
  passgen delete mydb`)
}

func main() {
    // Global --store parameter
    storeFile := defaultStoreFile
    if len(os.Args) > 1 {
        for i := 1; i < len(os.Args); i++ {
            if os.Args[i] == "--store" && i+1 < len(os.Args) {
                storeFile = os.Args[i+1]
                // Remove the store parameter
                os.Args = append(os.Args[:i], os.Args[i+2:]...)
                break
            }
        }
    }

    store := NewPasswordStore(storeFile)

    if len(os.Args) < 2 {
        printUsage()
        return
    }

    command := os.Args[1]
    args := os.Args[2:]

    switch command {
    case "generate":
        cmdGenerate(args)
    case "save":
        cmdSave(args, store)
    case "get":
        cmdGet(args, store)
    case "list":
        cmdList(args, store)
    case "delete":
        cmdDelete(args, store)
    case "export":
        cmdExport(args, store)
    case "help", "--help", "-h":
        printUsage()
    default:
        fmt.Fprintf(os.Stderr, "Unknown command: %s\n", command)
        fmt.Fprintf(os.Stderr, "Run 'passgen help' to see available commands\n")
        os.Exit(1)
    }
}

الغرض من الاستخدام:

BASH
# Generate password
$ go run main.go generate --length 16 --special
Kd9#mP2$xL7qR!vB

# Save password (auto-generated)
$ go run main.go save mydb --length 20
Saved: mydb = aB3xK9mP$L7qR!vD2nFg

# Save password (manually specified)
$ go run main.go save admin --password "MyStr0ng!Pass"
Saved: admin = MyStr0ng!Pass

# Retrieve password
$ go run main.go get mydb
mydb: aB3xK9mP$L7qR!vD2nFg

# List
$ go run main.go list
Name                 Password              Created
------------------------------------------------------------
mydb                 aB3xK9mP$L7qR!v...   2026-07-08
admin                MyStr0ng!Pass         2026-07-08

# Export
$ go run main.go export
Exported to: passwords_export.json

# Specify storage file
$ go run main.go --store vault.json generate -l 10
100%
flowchart TD
    A[passgen command] --> B{switch command}
    B -->|generate| C[flag.NewFlagSet parse<br/>--length --special --count]
    B -->|save| D[Parse name + --password<br/>Auto-generate or specify password]
    B -->|get| E[Look up by name from map]
    B -->|list| F[Iterate map, output table]
    B -->|delete| G[delete(map, name)]
    B -->|export| H[json.Marshal + WriteFile]
    C --> I[rand.Int secure random]
    D --> J[store.Add → json → save]
    E --> K[store.Get → output]
    F --> L[store.List → format]
    G --> M[store.Delete → json → save]
    H --> N[store.List → json.Marshal]
🔥 خطأ شائع: يُستخدم flag.NewFlagSet للأوامر الفرعية، ولكل أمر فرعي مجموعة علاماته الخاصة. أما flag.FlagSet.Parse فيقوم فقط بتحليل معطياته الخاصة ولا يُعدِّل os.Args. ويجب على الأمر الرئيسي تعيين القيم يدويًّا باستخدام os.Args.



❓ أسئلة شائعة

س كيف أختار بين flag وcobra؟
ج البرامج النصية البسيطة (أمر واحد، بضعة معلمات) → flag؛ الأدوات متعددة الأوامر (على غرار git: tool <command> [options]) → cobra. flag جزء من المكتبة القياسية، بينما cobra يوفر ميزات أكثر ولكنه يتطلب إضافة تبعيات.
س كيف يمكنني تحليل الأوامر الفرعية؟
ج الطريقة اليدوية: استخدم os.Args[1] لتحديد الأمر، وflag.NewFlagSet("sub", ...) لتحليل معلمات الأمر الفرعي. طريقة Cobra: استخدم rootCmd.AddCommand(subCmd)، وسيقوم الإطار بالتعامل مع الأمر تلقائيًا.
س كيف يمكنني التمرير عبر os.Args؟
ج os.Args[0] هو مسار البرنامج، وos.Args[1:] هي المعلمات. يمكنك التمرير عبرها باستخدام os.Args[i] أو range os.Args[1:].
س كيف يتم استخدام عمليات الإدخال/الإخراج للملفات في واجهة الأوامر (CLI)؟
ج هناك ثلاثة أوضاع: (1) الحفظ في مسار ثابت (~/.config/passgen.json)؛ (2) تحديد مسار باستخدام المعلمة --store؛ (3) متغيرات البيئة (مثل PASSGEN_STORE).
س كيف يمكنني نشر واجهة سطر الأوامر (CLI) على GitHub؟
ج (1) قم بالترجمة باستخدام go build -o mytool؛ (2) قم بالتحميل إلى GitHub Releases؛ (3) قم بالتثبيت باستخدام go install github.com/user/mytool@latest. تجعل الملفات الثنائية الثابتة لـ Go عملية النشر في غاية البساطة.
س كيف يضمن مُنشئ كلمات المرور الأمان؟
ج استخدم crypto/rand بدلاً من math/rand — حيث يستخدم crypto/rand مولد الأرقام العشوائية المعتمد على نظام التشغيل (CSPRNG) (مثل /dev/urandom)، وهو مولد لا يمكن التنبؤ بنتائجه. أما math/rand فهو مولد أرقام شبه عشوائية ولا يصلح لأغراض التشفير.
س كيف يمكن لأداة واجهة سطر الأوامر (CLI) معالجة المدخلات البسيطة من المستخدم؟
ج استخدم bufio.NewScanner(os.Stdin) للقراءة سطراً سطراً، أو fmt.Scanf لقراءة المدخلات المنسقة. بالنسبة لأدوات الإنتاج، نوصي باستخدام مكتبة gopass (التي لا تعرض كلمات المرور).
س كيف ينبغي التعامل مع رسائل الخطأ في واجهة سطر الأوامر (CLI)؟
ج ينبغي كتابة رسائل الخطأ في os.Stderr (بدلاً من os.Stdout) حتى يعمل mytool 2>/dev/null دون إظهار أي رسائل. استخدم fmt.Fprintln(os.Stderr, "error").

📖 ملخص


📝 تمارين

  1. المشكلة الأساسية (الصعوبة ⭐): استخدم حزمة flag لكتابة نسخة مطابقة لـ wc: تدعم mywc ثلاثة معلمات — --lines و--words و--chars — وتقرأ المدخلات من stdin أو من ملف، وتُخرج الإحصائيات.

  2. تمرين متقدم (مستوى الصعوبة ⭐⭐): أضف دعمًا لمتغير البيئة --store إلى مدير كلمات المرور في هذا الدرس: إذا تم تعيين متغير البيئة PASSGEN_STORE، فاستخدمه كمسار التخزين أولاً؛ وإلا، فاستخدم المعلمة --store؛ وأخيرًا، استخدم المسار الافتراضي.

  3. التحدي (الصعوبة: ⭐⭐⭐): أضف الأمر stats إلى مدير كلمات المرور: احسب العدد الإجمالي لكلمات المرور، وأطول وأقصر كلمة مرور، والأحرف الأكثر استخدامًا، والإحصائيات المجمعة حسب وقت الإنشاء (اليوم/هذا الأسبوع/هذا الشهر/سابقًا). يجب عليك استخدام sort.Slice + strings.Count + time.Time.

Web-Tutorial.com

فريق Web-Tutorial التقني

منصة دروس برمجية يديرها عدة مطورين. كل درس يتم كتابته ومراجعته بواسطة مطورين متخصصين في المجال. نعمل على ضمان دقة وموثوقية المحتوى — إذا لاحظت أي مشكلة، فيرجى إخبارنا.

100%