Go: التطبيق العملي: تطوير أدوات واجهة سطر الأوامر
آخر تحديث: 2026-08-26
يُعد تطوير أدوات واجهة سطر الأوامر (CLI) إحدى نقاط قوة لغة Go — فهي تُترجم إلى ملف ثنائي واحد، وتعمل عبر الأنظمة المختلفة، ولا تحتوي على أي تبعيات. وقد أصبح كتابة أدوات واجهة سطر الأوامر بلغة Go هو المعيار السائد في عصر التطبيقات السحابية الأصلية.
بدءًا من docker مرورًا بـ kubectl وصولاً إلى gh، فإن جميع أدوات واجهة الأوامر (CLI) السائدة تقريبًا مكتوبة بلغة Go. في هذا الدرس، ستتقن جميع المهارات الأساسية لتطوير واجهة الأوامر (CLI) وستقوم بإنشاء برنامج كامل لإدارة كلمات المرور.
1. ستتعلم
- تقوم الحزمة
flagبتحليل معلمات سطر الأوامر os.Argsالوصول إلى الحجج الأولية- تدفقات قياسية
os.Stdin/os.Stdout - مكتبة «كوبرا» ونمط الأوامر الفرعية
- تكامل واجهة سطر الأوامر (CLI) وعمليات الإدخال/الإخراج للملفات
- المشروع: واجهة سطر الأوامر لمدير كلمات المرور (إنشاء/تخزين/استرداد/تصدير كلمات المرور)
2. قصة حقيقية لمهندس عمليات
(1) المشكلة: الاضطرار إلى إنشاء كلمة مرور يدويًّا في كل مرة
بوب هو مهندس عمليات يحتاج في كثير من الأحيان إلى إنشاء كلمات مرور معقدة لمختلف الخدمات:
«اليوم أقوم بإنشاء كلمة مرور لـ PostgreSQL، وغدًا لـ Redis، وبعد غد لحساب إداري. وفي كل مرة، أضطر إلى فتح موقع ويب لإنشاء كلمات المرور، ثم نسخها ولصقها في 1Password — خمس مرات في اليوم. هذا أمر مزعج للغاية. علاوة على ذلك، تختلف متطلبات المواقع الإلكترونية من موقع لآخر: 12 حرفًا مع أحرف خاصة، أو 16 حرفًا بدون أحرف غامضة...»
يبحث بوب عن أداة CLI مثل هذه:
# 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) حل لعبة الغو: حزمة الأعلام + عمليات الإدخال/الإخراج للملفات
// 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))
}
}
$ 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 build، يمكنك ببساطة نسخه إلى أي خادم يعمل بنظام Linux أو Mac أو Windows لتشغيله. وهذه هي الميزة الأساسية لاستخدام Go في تطبيقات واجهة الأوامر (CLI).
3. حزمة flag: تحليل معلمات سطر الأوامر
(1) أساسيات الأعلام
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)
}
$ 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 |
▶ مثال: معلمة قصيرة مع علامة + معلمة بدون علامة
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
}
$ 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: المعلمات الأولية
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)
}
}
$ 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)
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: مدخلات الأنبوب
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)")
}
}
}
$ echo "hello world" | go run main.go
HELLO WORLD
$ go run main.go hello world
HELLO WORLD
▶ مثال: الإدخال التفاعلي (fmt.Scanf)
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
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» (الذي كتبه مبتكر «كوبيرنيتس»)، وهو يدعم الأوامر الفرعية ورسائل المساعدة والإكمال التلقائي.
# 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»
// 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)
}
}
// 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)
}
// main.go
package main
import "passgen/cmd"
func main() {
cmd.Execute()
}
$ go build -o passgen
$ ./passgen generate --length 16 --special
Kd9#mP2$xL7qR!vB
$ ./passgen generate -l 8
aB3xK9mP
(3) فلاغ مقابل كوبرا
| البعد | حزمة الإشارات | كوبرا |
|---|---|---|
| مدمج | ✅ المكتبة القياسية | ❌ طرف ثالث |
| الأمر الفرعي | ❌ غير مدعوم | ✅ مدعوم أصلاً |
| المساعدة في مجال السيارات | ✅ الأساسيات | ✅ الجماليات |
| الإكمال التلقائي | ❌ | ✅ bash/zsh/fish |
| حالات الاستخدام | البرامج النصية البسيطة | الأدوات المعقدة متعددة الأوامر |
6. مثال كامل: واجهة سطر الأوامر لبرنامج إدارة كلمات المرور
اجمع كل هذه المعرفة في أداة شاملة لإدارة كلمات المرور:
// 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)
}
}
الغرض من الاستخدام:
# 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
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:].~/.config/passgen.json)؛ (2) تحديد مسار باستخدام المعلمة --store؛ (3) متغيرات البيئة (مثل PASSGEN_STORE).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 فهو مولد أرقام شبه عشوائية ولا يصلح لأغراض التشفير.bufio.NewScanner(os.Stdin) للقراءة سطراً سطراً، أو fmt.Scanf لقراءة المدخلات المنسقة. بالنسبة لأدوات الإنتاج، نوصي باستخدام مكتبة gopass (التي لا تعرض كلمات المرور).os.Stderr (بدلاً من os.Stdout) حتى يعمل mytool 2>/dev/null دون إظهار أي رسائل. استخدم fmt.Fprintln(os.Stderr, "error").📖 ملخص
- تقوم حزمة
flagبتحليل معلمات سطر الأوامر:flag.String/flag.Int/flag.Bool os.Argsيتيح الوصول إلى المعلمات الأولية، مما يتيح إمكانية التحليل اليدوي المرن- تدفقات الإدخال/الإخراج القياسية
os.Stdin/os.Stdout/os.Stderr flag.NewFlagSetيُنفِّذ واجهة سطر الأوامر (CLI) على غرار الأوامر الفرعية- توفر مكتبة «كوبرا» إطار عمل متكاملًا للأوامر الفرعية
crypto/randيُولِّد أرقامًا عشوائية آمنة من الناحية التشفيرية- الاحتفاظ بالبيانات لأدوات واجهة سطر الأوامر (CLI): ملفات JSON + ذاكرة التخزين المؤقت للخريطة داخل الذاكرة
- يتم ترجمة
go buildإلى ملف ثنائي واحد لتوزيعه عبر الأنظمة المختلفة
📝 تمارين
-
المشكلة الأساسية (الصعوبة ⭐): استخدم حزمة
flagلكتابة نسخة مطابقة لـwc: تدعمmywcثلاثة معلمات —--linesو--wordsو--chars— وتقرأ المدخلات من stdin أو من ملف، وتُخرج الإحصائيات. -
تمرين متقدم (مستوى الصعوبة ⭐⭐): أضف دعمًا لمتغير البيئة
--storeإلى مدير كلمات المرور في هذا الدرس: إذا تم تعيين متغير البيئةPASSGEN_STORE، فاستخدمه كمسار التخزين أولاً؛ وإلا، فاستخدم المعلمة--store؛ وأخيرًا، استخدم المسار الافتراضي. -
التحدي (الصعوبة: ⭐⭐⭐): أضف الأمر
statsإلى مدير كلمات المرور: احسب العدد الإجمالي لكلمات المرور، وأطول وأقصر كلمة مرور، والأحرف الأكثر استخدامًا، والإحصائيات المجمعة حسب وقت الإنشاء (اليوم/هذا الأسبوع/هذا الشهر/سابقًا). يجب عليك استخدامsort.Slice+strings.Count+time.Time.