وبلاگ
نوشتن ابزارهای خط فرمان (CLI) با Go
فهرست مطالب
“تسلط به برنامهنویسی پایتون با هوش مصنوعی: آموزش کدنویسی هوشمند با ChatGPT”
"تسلط به برنامهنویسی پایتون با هوش مصنوعی: آموزش کدنویسی هوشمند با ChatGPT"
"با شرکت در این دوره جامع و کاربردی، به راحتی مهارتهای برنامهنویسی پایتون را از سطح مبتدی تا پیشرفته با کمک هوش مصنوعی ChatGPT بیاموزید. این دوره، با بیش از 6 ساعت محتوای آموزشی، شما را قادر میسازد تا به سرعت الگوریتمهای پیچیده را درک کرده و اپلیکیشنهای هوشمند ایجاد کنید. مناسب برای تمامی سطوح با زیرنویس فارسی حرفهای و امکان دانلود و تماشای آنلاین."
ویژگیهای کلیدی:
بدون نیاز به تجربه قبلی برنامهنویسی
زیرنویس فارسی با ترجمه حرفهای
۳۰ ٪ تخفیف ویژه برای دانشجویان و دانش آموزان
0 تا 100 عطرسازی + (30 فرمولاسیون اختصاصی حامی صنعت)
دوره آموزش Flutter و برنامه نویسی Dart [پروژه محور]
دوره جامع آموزش برنامهنویسی پایتون + هک اخلاقی [با همکاری شاهک]
دوره جامع آموزش فرمولاسیون لوازم آرایشی
دوره جامع علم داده، یادگیری ماشین، یادگیری عمیق و NLP
دوره فوق فشرده مکالمه زبان انگلیسی (ویژه بزرگسالان)
شمع سازی و عودسازی با محوریت رایحه درمانی
صابون سازی (دستساز و صنعتی)
صفر تا صد طراحی دارو
متخصص طب سنتی و گیاهان دارویی
متخصص کنترل کیفی شرکت دارویی
نوشتن ابزارهای خط فرمان (CLI) با Go
در دنیای توسعه نرمافزار، ابزارهای خط فرمان (Command-Line Interface یا CLI) نقش حیاتی در خودکارسازی وظایف، مدیریت سیستمها و تعامل کارآمد با برنامهها ایفا میکنند. از ابزارهای سیستمی گرفته تا اسکریپتهای مدیریت ابری و توسعه، CLIها کارایی بینظیری را برای توسعهدهندگان و مدیران سیستم به ارمغان میآورند. در میان زبانهای برنامهنویسی مختلف، Go (یا Golang) به دلیل ویژگیهای منحصربهفرد خود، به انتخابی ایدهآل برای ساخت ابزارهای CLI قدرتمند، کارآمد و قابل حمل تبدیل شده است.
این مقاله به بررسی عمیق فرایند ساخت ابزارهای CLI با Go میپردازد. ما از مبانی تا مفاهیم پیشرفته، شامل مدیریت آرگومانها، استفاده از کتابخانههای محبوب، بهترین شیوههای توسعه، و استراتژیهای استقرار و توزیع را پوشش خواهیم داد. هدف این راهنما توانمندسازی شما برای نوشتن ابزارهای CLI حرفهای و با کیفیت بالا است که به طور مؤثر نیازهای پروژههای شما را برآورده کنند.
چه شما یک توسعهدهنده Go باتجربه باشید که به دنبال عمیقتر شدن در حوزه CLI است، و چه تازه وارد این زبان شدهاید و میخواهید اولین ابزار خود را بسازید، این مقاله بینشها و دانش عملی لازم را برای دستیابی به اهدافتان فراهم میکند. با تمرکز بر شفافیت، کارایی و قابلیت اطمینان، Go ابزارهای لازم را برای ایجاد راهکارهای خط فرمان مقیاسپذیر و قابل نگهداری در اختیار شما قرار میدهد.
چرا Go برای توسعه ابزارهای CLI انتخاب مناسبی است؟
انتخاب زبان مناسب برای توسعه ابزارهای خط فرمان میتواند تأثیر بسزایی بر عملکرد، قابلیت نگهداری و سهولت توزیع ابزار نهایی داشته باشد. Go به دلایل متعددی به عنوان یک انتخاب برجسته برای این منظور شناخته میشود. درک این مزایا به شما کمک میکند تا تصمیم بگیرید چرا Go ابزار مناسبی برای پروژه CLI بعدی شماست.
باینریهای مستقل و کامپایل متقاطع (Static Binaries & Cross-Compilation)
یکی از بزرگترین مزایای Go، قابلیت تولید باینریهای مستقل (statically linked binaries) است. این به این معنی است که فایل اجرایی نهایی شامل تمام وابستگیهای لازم است و برای اجرا نیازی به نصب هیچ runtime یا کتابخانه اضافی در سیستم مقصد ندارد. این ویژگی توزیع و استقرار ابزارهای Go CLI را به شدت ساده میکند. دیگر نیازی نیست نگران ناسازگاری نسخههای پایتون، روبی، Node.js یا JVM باشید؛ فایل اجرایی Go فقط کار میکند!
علاوه بر این، Go از کامپایل متقاطع (cross-compilation) به صورت بومی پشتیبانی میکند. این قابلیت به توسعهدهندگان اجازه میدهد تا یک باینری برای سیستمعاملها و معماریهای مختلف (مانند Windows، macOS، Linux، ARM64 و x86_64) را از یک سیستم واحد کامپایل کنند. به عنوان مثال، میتوانید یک ابزار CLI برای Windows را روی یک ماشین Linux کامپایل کرده و آن را به سادگی توزیع کنید. این ویژگی برای توسعهدهندگانی که ابزارهای خود را برای مخاطبان گستردهای منتشر میکنند، بسیار ارزشمند است.
GOOS=windows GOARCH=amd64 go build -o mytool.exe main.go
GOOS=darwin GOARCH=arm64 go build -o mytool-mac-arm64 main.go
عملکرد بالا و کارایی (High Performance & Efficiency)
Go یک زبان کامپایلشده است که به سرعت و کارایی بالایی دست مییابد. این موضوع برای ابزارهای CLI که ممکن است نیاز به پردازش حجم زیادی از دادهها، انجام عملیات پیچیده محاسباتی، یا تعامل سریع با سیستمعامل داشته باشند، بسیار حیاتی است. عملکرد Go معمولاً با زبانهایی مانند C++ و Rust قابل مقایسه است، در حالی که پیچیدگیهای مدیریت حافظه دستی را ندارد.
همزمانی و Concurrency (Goroutines & Channels)
Go با مفهوم Goroutine و Channel، پشتیبانی بومی و قدرتمندی از همزمانی (concurrency) ارائه میدهد. Goroutineها توابع سبکی هستند که میتوانند به طور موازی اجرا شوند و Channelها راهی امن برای ارتباط بین آنها فراهم میکنند. این ویژگی Go را برای توسعه ابزارهای CLI که نیاز به انجام چندین وظیفه به طور همزمان دارند (مانند فراخوانی APIهای متعدد، پردازش فایلهای موازی، یا گوش دادن به چندین ورودی) بسیار مناسب میسازد. به عنوان مثال، یک ابزار CLI برای پشتیبانگیری میتواند همزمان چندین فایل را آپلود کند و یک ابزار deployment میتواند همزمان به چندین سرور SSH کند.
کتابخانه استاندارد غنی (Rich Standard Library)
Go دارای یک کتابخانه استاندارد بسیار جامع و با کیفیت است که بسیاری از نیازهای اساسی توسعه CLI را پوشش میدهد. این کتابخانه شامل پکیجهایی برای:
- `os`: تعامل با سیستمعامل، دسترسی به آرگومانهای خط فرمان (`os.Args`)، متغیرهای محیطی، فایلها و دایرکتوریها.
- `flag`: تجزیه و تحلیل فلگهای خط فرمان.
- `fmt`: فرمتبندی و چاپ خروجی.
- `io`, `bufio`, `ioutil`: عملیات ورودی/خروجی.
- `net/http`: ساخت کلاینتهای HTTP برای تعامل با APIها.
- `encoding/json`, `encoding/xml`: کار با فرمتهای داده.
- `strings`, `strconv`: عملیات روی رشتهها و تبدیل نوع داده.
این غنای کتابخانه استاندارد به معنای وابستگی کمتر به کتابخانههای خارجی است که میتواند اندازه باینری نهایی را کوچکتر نگه داشته و فرایند توسعه را سادهتر کند.
جامعه و اکوسیستم (Community & Ecosystem)
جامعه Go فعال و در حال رشد است. این بدان معنی است که شما به تعداد زیادی منابع، آموزشها، و کتابخانههای شخص ثالث دسترسی دارید که میتوانند فرایند توسعه CLI شما را تسهیل کنند. کتابخانههای محبوبی مانند Cobra، Viper، و pflag به طور خاص برای سادهسازی ساخت ابزارهای CLI پیچیده در Go طراحی شدهاند و بسیاری از چالشهای رایج را حل میکنند.
پشتیبانی از مدیریت وابستگیها (Dependency Management)
Go Modules (از Go 1.11 به بعد) یک سیستم مدیریت وابستگی بومی و قدرتمند را فراهم میکند. این سیستم به شما کمک میکند تا وابستگیهای پروژه خود را به طور مؤثر مدیریت کنید، بازتولیدپذیری ساخت (reproducible builds) را تضمین کرده و از تداخل نسخهها جلوگیری کنید. این ویژگی برای پروژههای CLI که ممکن است به چندین کتابخانه خارجی وابسته باشند، بسیار مهم است.
سادگی و خوانایی (Simplicity & Readability)
Go با یک سینتکس تمیز و ساده طراحی شده است که خوانایی کد را افزایش میدهد. این موضوع به خصوص در پروژههای بزرگتر یا تیمهایی که چندین توسعهدهنده روی یک ابزار CLI کار میکنند، اهمیت پیدا میکند. قوانین فرمتبندی سختگیرانه (`go fmt`) و ابزارهای استاتیک تحلیل کد (`go vet`) نیز به حفظ کیفیت و سازگاری کد کمک میکنند.
با توجه به این مزایا، Go به عنوان یک انتخاب بینظیر برای توسعهدهندگانی که به دنبال ساخت ابزارهای CLI سریع، قابل اعتماد، قابل حمل و آسان برای توزیع هستند، مطرح میشود.
شروع کار با یک ابزار CLI ساده در Go
برای شروع توسعه یک ابزار CLI با Go، ابتدا نیاز است که اصول اولیه نحوه دریافت ورودی از خط فرمان و تولید خروجی را درک کنیم. این بخش شما را با ساختار یک برنامه Go CLI ساده و نحوه تعامل آن با محیط خط فرمان آشنا میکند.
ساختار پایه یک برنامه Go CLI
هر برنامه Go با تابع `main` در پکیج `main` آغاز میشود. این نقطه ورود برنامه شماست.
package main
import (
"fmt"
"os"
)
func main() {
// کد CLI شما در اینجا قرار میگیرد
fmt.Println("سلام از ابزار CLI من!")
}
برای اجرای این کد، آن را در فایلی به نام `main.go` ذخیره کرده و سپس در ترمینال دستور `go run main.go` را اجرا کنید. خروجی “سلام از ابزار CLI من!” را مشاهده خواهید کرد.
خواندن آرگومانهای خط فرمان با `os.Args`
ابزارهای CLI برای انجام کارهای مفید نیاز به دریافت ورودی از کاربر دارند. یکی از سادهترین راهها برای دریافت ورودی، استفاده از آرگومانهای خط فرمان است که پس از نام برنامه وارد میشوند. در Go، این آرگومانها از طریق اسلایس `os.Args` در دسترس هستند.
`os.Args` یک اسلایس از رشتهها است که شامل نام برنامه در ایندکس 0 و سپس هر آرگومان به ترتیب در ایندکسهای بعدی است.
package main
import (
"fmt"
"os"
"strings"
)
func main() {
// os.Args شامل نام برنامه در ایندکس 0 است.
// بنابراین، آرگومانهای واقعی از ایندکس 1 شروع میشوند.
args := os.Args[1:] // برش اسلایس برای حذف نام برنامه
if len(args) == 0 {
fmt.Println("لطفاً نامی را به عنوان آرگومان وارد کنید.")
fmt.Println("مثال: go run main.go جهان")
os.Exit(1) // خروج با کد خطا
return
}
name := strings.Join(args, " ") // اگر چندین کلمه وارد شده باشد، آنها را به هم وصل کنید
fmt.Printf("سلام، %s! خوش آمدید.\n", name)
}
مثال اجرای کد بالا:
$ go run main.go
لطفاً نامی را به عنوان آرگومان وارد کنید.
مثال: go run main.go جهان
$ go run main.go علی
سلام، علی! خوش آمدید.
$ go run main.go تیم برنامه نویسی
سلام، تیم برنامه نویسی! خوش آمدید.
در این مثال، `os.Exit(1)` برای نشان دادن اینکه برنامه با خطا خارج شده است، استفاده میشود. کد خروجی 0 معمولاً به معنای اجرای موفقیتآمیز و هر عدد غیر صفر به معنای خطا است.
تجزیه فلگهای ساده (بدون کتابخانه خارجی)
علاوه بر آرگومانهای موقعیتی، بسیاری از ابزارهای CLI از “فلگها” (flags) یا “آپشنها” (options) برای پیکربندی رفتار خود استفاده میکنند (مثلاً `-v` برای verbose، `–help` برای راهنما). برای فلگهای بسیار ساده، میتوانیم آنها را به صورت دستی تجزیه کنیم، اگرچه برای موارد پیچیدهتر، استفاده از پکیج `flag` استاندارد یا کتابخانههای شخص ثالث مانند Cobra به شدت توصیه میشود.
package main
import (
"fmt"
"os"
)
func main() {
// یک فلگ ساده --verbose را به صورت دستی بررسی کنید
verbose := false
name := "کاربر" // مقدار پیشفرض
for i, arg := range os.Args {
if i == 0 {
continue // رد کردن نام برنامه
}
switch arg {
case "--verbose", "-v":
verbose = true
case "--name":
if i+1 < len(os.Args) {
name = os.Args[i+1]
i++ // آرگومان بعدی را رد کنید چون نام را مصرف کردیم
} else {
fmt.Println("خطا: برای --name مقدار لازم است.")
os.Exit(1)
}
default:
// اگر آرگومانهای دیگر وجود داشته باشد، میتوانند به عنوان آرگومانهای موقعیتی در نظر گرفته شوند
// در این مثال، ما فقط به فلگها و --name علاقه داریم
}
}
if verbose {
fmt.Println("حالت Verbose فعال است.")
}
fmt.Printf("سلام، %s!\n", name)
}
مثالهای اجرا:
$ go run main.go
سلام، کاربر!
$ go run main.go -v
حالت Verbose فعال است.
سلام، کاربر!
$ go run main.go --name "سئو نویس"
سلام، سئو نویس!
$ go run main.go -v --name "توسعه دهنده"
حالت Verbose فعال است.
سلام، توسعه دهنده!
همانطور که میبینید، تجزیه دستی فلگها میتواند به سرعت پیچیده شود، به خصوص زمانی که انواع مختلف فلگها (بولین، رشته، عدد)، مقادیر پیشفرض، مقادیر اجباری و پیامهای راهنما مطرح میشوند. به همین دلیل، در بخش بعدی به بررسی راهحلهای استاندارد و پیشرفتهتر برای مدیریت آرگومانها و فلگها خواهیم پرداخت.
این بخش یک شروع قدرتمند برای درک نحوه عملکرد ابزارهای CLI در Go فراهم میکند. با فهمیدن چگونگی دسترسی به `os.Args` و انجام تجزیه دستی ساده، شما آمادهاید تا به ابزارهای قدرتمندتری بپردازید که Go برای ساخت CLIهای پیچیدهتر ارائه میدهد.
مدیریت آرگومانها و فلگها: استاندارد و کتابخانههای قدرتمند
برای ساخت ابزارهای CLI واقعی و کاربردی، مدیریت آرگومانها و فلگها به روشی ساختارمند و قابل اعتماد ضروری است. Go چندین گزینه برای این کار ارائه میدهد، از پکیج `flag` استاندارد گرفته تا کتابخانههای شخص ثالث قدرتمند مانند Cobra و Viper.
پکیج `flag` استاندارد Go
پکیج `flag` بخشی از کتابخانه استاندارد Go است و یک راه ساده و مؤثر برای تعریف و تجزیه فلگهای خط فرمان فراهم میکند. این پکیج برای ابزارهای CLI با تعداد محدود فلگ و بدون نیاز به زیرفرمانها (subcommands) بسیار مناسب است.
تعریف و تجزیه فلگها
برای تعریف یک فلگ، از توابعی مانند `flag.String()`, `flag.Int()`, `flag.Bool()` و غیره استفاده میکنید. هر یک از این توابع سه آرگومان میگیرد: نام فلگ، مقدار پیشفرض، و یک رشته توضیحات (که در پیام راهنما استفاده میشود).
پس از تعریف همه فلگها، باید `flag.Parse()` را فراخوانی کنید تا مقادیر فلگها از خط فرمان تجزیه و به متغیرهای شما اختصاص داده شوند. آرگومانهای باقیمانده (که فلگ نیستند) پس از `flag.Parse()` از طریق `flag.Args()` در دسترس خواهند بود.
package main
import (
"flag"
"fmt"
)
func main() {
// 1. تعریف فلگها
// flag.String(name, defaultValue, description)
name := flag.String("name", "کاربر", "نامی که قرار است سلام داده شود")
// flag.Bool(name, defaultValue, description)
verbose := flag.Bool("verbose", false, "فعال کردن حالت پرجزئیات")
// flag.Int(name, defaultValue, description)
count := flag.Int("count", 1, "تعداد دفعات تکرار سلام")
// 2. تجزیه فلگها از خط فرمان
flag.Parse()
// 3. استفاده از مقادیر فلگها
if *verbose {
fmt.Println("حالت پرجزئیات فعال است.")
fmt.Printf("فلگ name: %s, فلگ verbose: %t, فلگ count: %d\n", *name, *verbose, *count)
}
for i := 0; i < *count; i++ {
fmt.Printf("سلام، %s!\n", *name)
}
// دسترسی به آرگومانهای غیر فلگ (موقعیتی)
remainingArgs := flag.Args()
if len(remainingArgs) > 0 {
fmt.Println("\nآرگومانهای اضافی (غیر فلگ):")
for _, arg := range remainingArgs {
fmt.Println("-", arg)
}
}
}
مثالهای اجرا با پکیج `flag`:
$ go run main.go
سلام، کاربر!
$ go run main.go --name "جهان" -verbose
حالت پرجزئیات فعال است.
فلگ name: جهان, فلگ verbose: true, فلگ count: 1
سلام، جهان!
$ go run main.go -name "تیم" -count 3
سلام، تیم!
سلام، تیم!
سلام، تیم!
$ go run main.go --help
Usage of C:\Users\user\AppData\Local\Temp\go-build...\main.exe:
-count int
تعداد دفعات تکرار سلام (default 1)
-name string
نامی که قرار است سلام داده شود (default "کاربر")
-verbose
فعال کردن حالت پرجزئیات (default false)
پکیج `flag` به صورت خودکار پیام راهنما را در صورت استفاده از `--help` یا `-h` تولید میکند.
کتابخانههای قدرتمند شخص ثالث: Cobra و Viper
برای ابزارهای CLI پیچیدهتر که دارای زیرفرمانها (subcommands) زیاد، فلگهای مشترک بین دستورات مختلف و نیاز به مدیریت پیکربندی از فایلها یا متغیرهای محیطی هستند، کتابخانههایی مانند Cobra و Viper بسیار کارآمدتر هستند.
Cobra: چارچوبی برای CLIهای مدرن
Cobra یکی از محبوبترین کتابخانههای Go برای ساخت CLI است که الهامگرفته از Git و Docker است. این کتابخانه ساختاری قوی برای تعریف دستورات، زیرفرمانها، فلگها، و مدیریت ورودی و خروجی فراهم میکند. بسیاری از ابزارهای شناختهشده Go مانند `kubectl`، `Hugo` و `Docker` از Cobra استفاده میکنند.
ویژگیهای کلیدی Cobra:
- دستورات و زیرفرمانها (Commands & Subcommands): اجازه میدهد تا CLIهای سازمانیافته با سلسلهمراتبی از دستورات (مثلاً `git clone`, `docker ps`).
- فلگها (Flags): پشتیبانی کامل از انواع فلگها، فلگهای محلی و سراسری (persistent flags).
- اعتبار سنجی آرگومانها: کنترل دقیق تعداد و نوع آرگومانهای هر دستور.
- Hooks: توابع `PersistentPreRun`, `PreRun`, `PostRun`, `PersistentPostRun` برای اجرای منطق قبل و بعد از دستور.
- پیامهای راهنما و استفاده: تولید خودکار پیامهای راهنما و استفاده برای هر دستور و فلگ.
شروع کار با Cobra:
ابتدا باید Cobra را نصب کنید:
go get github.com/spf13/cobra/cobra
سپس میتوانید از ابزار `cobra-cli` برای ایجاد اسکلت پروژه خود استفاده کنید:
cobra-cli init --pkg-name your_module_name
cobra-cli add serve
cobra-cli add config
یک مثال ساده با Cobra:
// main.go
package main
import (
"your_module_name/cmd" // مسیر ماژول شما
)
func main() {
cmd.Execute()
}
// cmd/root.go
package cmd
import (
"fmt"
"os"
"github.com/spf13/cobra"
)
var (
verbose bool
name string
)
// rootCmd represents the base command when called without any subcommands
var rootCmd = &cobra.Command{
Use: "mycli",
Short: "یک ابزار CLI ساده با Cobra",
Long: `این ابزار یک مثال برای نمایش قابلیتهای Cobra است.
شامل زیرفرمانها و فلگهای مختلف میشود.`,
// PersistentPreRunE: func(cmd *cobra.Command, args []string) error {
// // این تابع قبل از هر دستوری اجرا میشود
// if verbose {
// fmt.Println("حالت پرجزئیات فعال است (از PersistentPreRunE).")
// }
// return nil
// },
Run: func(cmd *cobra.Command, args []string) {
// این تابع زمانی اجرا میشود که هیچ زیرفرمانی داده نشده باشد
if verbose {
fmt.Println("حالت پرجزئیات فعال است.")
}
if name != "" {
fmt.Printf("سلام، %s از root command!\n", name)
} else {
fmt.Println("سلام از root command! برای استفاده از نام، فلگ --name را به کار ببرید.")
}
},
}
// Execute adds all child commands to the root command and sets flags appropriately.
// This is called by main.main(). It only needs to happen once to the rootCmd.
func Execute() {
if err := rootCmd.Execute(); err != nil {
fmt.Fprintln(os.Stderr, err)
os.Exit(1)
}
}
func init() {
// اینجا فلگهای سراسری (persistent flags) را تعریف میکنیم
// این فلگها برای همه زیرفرمانها نیز در دسترس هستند
rootCmd.PersistentFlags().BoolVarP(&verbose, "verbose", "v", false, "فعال کردن حالت پرجزئیات")
rootCmd.PersistentFlags().StringVarP(&name, "name", "n", "", "نامی که قرار است سلام داده شود")
// مثال یک فلگ محلی فقط برای root command
// rootCmd.Flags().BoolP("toggle", "t", false, "Help message for toggle")
}
// cmd/greet.go (یک زیرفرمان جدید)
package cmd
import (
"fmt"
"github.com/spf13/cobra"
)
var (
message string
)
// greetCmd represents the greet command
var greetCmd = &cobra.Command{
Use: "greet [name]",
Short: "سلام به یک شخص یا جهان",
Long: `این دستور یک پیام سلام را به نام ورودی یا به "جهان" نمایش میدهد.
مثال: mycli greet Go
مثال: mycli greet --message "سلام عالی" Go`,
Args: cobra.MaximumNArgs(1), // حداکثر 1 آرگومان موقعیتی
Run: func(cmd *cobra.Command, args []string) {
target := "جهان"
if len(args) > 0 {
target = args[0]
}
if message != "" {
fmt.Printf("%s، %s!\n", message, target)
} else {
fmt.Printf("سلام، %s!\n", target)
}
},
}
func init() {
rootCmd.AddCommand(greetCmd)
// فلگ محلی فقط برای دستور greet
greetCmd.Flags().StringVarP(&message, "message", "m", "", "پیام سفارشی برای سلام")
// اگر میخواهید فلگهای سراسری rootCmd را در اینجا هم استفاده کنید، نیاز به تعریف مجدد نیست.
// آنها به طور خودکار به این دستور هم منتقل میشوند.
}
مثالهای اجرا با Cobra:
$ go run main.go
سلام از root command! برای استفاده از نام، فلگ --name را به کار ببرید.
$ go run main.go -n "فردین"
سلام، فردین از root command!
$ go run main.go greet
سلام، جهان!
$ go run main.go greet Go
سلام، Go!
$ go run main.go greet Go -m "درود بر"
درود بر، Go!
$ go run main.go greet -v Go
حالت پرجزئیات فعال است.
سلام، Go!
$ go run main.go help
... (نمایش پیام راهنمای جامع) ...
$ go run main.go greet --help
... (نمایش پیام راهنمای دستور greet) ...
Cobra به شما اجازه میدهد تا ساختار CLI خود را به صورت درختگونه سازماندهی کنید، که برای ابزارهای پیچیده با چندین دستور منطقی بسیار مفید است.
Viper: مدیریت پیکربندی قدرتمند
Viper یک کتابخانه برای مدیریت پیکربندی در Go است که به خوبی با Cobra یکپارچه میشود. این امکان را به شما میدهد تا پیکربندی برنامه خود را از منابع مختلفی مانند فایلهای JSON، YAML، TOML، متغیرهای محیطی، فلگهای خط فرمان، و سیستمهای کلید-مقدار (key-value stores) بخوانید و آن را به صورت سلسلهمراتبی سازماندهی کنید.
ویژگیهای کلیدی Viper:
- پشتیبانی از فرمتهای مختلف (JSON, TOML, YAML, HCL, envfile).
- قابلیت خواندن از متغیرهای محیطی (environment variables).
- قابلیت تنظیم مقادیر پیشفرض (default values).
- مشاهده تغییرات فایل پیکربندی (watching config file changes).
- ادغام با `pflag` (که Cobra از آن استفاده میکند).
یکپارچهسازی Viper با Cobra:
// cmd/root.go (بخش init)
package cmd
import (
"fmt"
"os"
"github.com/spf13/cobra"
"github.com/spf13/viper"
)
var (
cfgFile string
verbose bool
name string
)
// ... (rootCmd و Execute همانند قبل) ...
func init() {
cobra.OnInitialize(initConfig) // این تابع را قبل از اجرای هر دستور فراخوانی میکند
rootCmd.PersistentFlags().StringVar(&cfgFile, "config", "", "مسیر فایل پیکربندی (پیشفرض $HOME/.mycli.yaml)")
rootCmd.PersistentFlags().BoolVarP(&verbose, "verbose", "v", false, "فعال کردن حالت پرجزئیات")
rootCmd.PersistentFlags().StringVarP(&name, "name", "n", "", "نامی که قرار است سلام داده شود")
}
func initConfig() {
if cfgFile != "" {
// استفاده از فایل پیکربندی مشخص شده در فلگ
viper.SetConfigFile(cfgFile)
} else {
// جستجوی فایل پیکربندی در دایرکتوریهای استاندارد
home, err := os.UserHomeDir()
if err != nil {
fmt.Fprintln(os.Stderr, "خطا در یافتن دایرکتوری Home:", err)
os.Exit(1)
}
viper.AddConfigPath(home) // جستجو در $HOME
viper.AddConfigPath(".") // جستجو در دایرکتوری جاری
viper.SetConfigName(".mycli") // نام فایل پیکربندی (مثلاً .mycli.yaml, .mycli.json)
}
viper.AutomaticEnv() // خواندن متغیرهای محیطی با پیشوند MYCLI_
if err := viper.ReadInConfig(); err == nil {
fmt.Fprintln(os.Stderr, "استفاده از فایل پیکربندی:", viper.ConfigFileUsed())
} else {
// میتوانید خطا را نادیده بگیرید اگر فایل پیکربندی اختیاری است
// fmt.Fprintln(os.Stderr, "خطا در خواندن فایل پیکربندی:", err)
}
// تنظیم مقادیر از پیکربندی (فلگها اولویت بالاتری دارند)
if name == "" { // اگر فلگ --name تنظیم نشده باشد
name = viper.GetString("default_name") // میتوانید کلیدها را از فایل پیکربندی بخوانید
}
}
با این ساختار، میتوانید یک فایل `~/.mycli.yaml` (یا .json) با محتوایی مانند زیر داشته باشید:
# ~/.mycli.yaml
default_name: "مدیر سیستم"
و یا یک متغیر محیطی:
export MYCLI_DEFAULT_NAME="تیم运维"
Viper به شما اجازه میدهد تا منطق پیچیده بارگذاری پیکربندی را به شکلی سازماندهی شده و قابل نگهداری پیادهسازی کنید و به ابزار CLI خود انعطافپذیری بیشتری بدهید.
دیگر گزینهها: urfave/cli و spf13/pflag
- `spf13/pflag`: این کتابخانه یک جایگزین کامل برای پکیج `flag` استاندارد Go است که از فلگهای سبک Unix (مثلاً `-a`, `-b`, `-c`) و POSIX (مثلاً `--long-option`) پشتیبانی میکند. Cobra از `pflag` استفاده میکند، بنابراین اگر از Cobra استفاده میکنید، به صورت خودکار از `pflag` نیز بهرهمند میشوید. میتوانید `pflag` را به تنهایی نیز برای پروژههایی که نیازی به ساختار کامل Cobra ندارند، استفاده کنید اما از فلگهای Unix-style بهرهمند شوید.
- `urfave/cli`: یک چارچوب CLI دیگر است که رویکرد متفاوتی نسبت به Cobra دارد. `urfave/cli` بیشتر بر پایه تعریف برنامه و دستورات به صورت تابعی (functional) تمرکز دارد و ممکن است برای برخی توسعهدهندگان، سینتکس آن سادهتر و سرراستتر باشد. این کتابخانه نیز امکانات کاملی برای فلگها، زیرفرمانها و تولید راهنما را فراهم میکند. انتخاب بین Cobra و `urfave/cli` اغلب به ترجیح شخصی و پیچیدگی پروژه بستگی دارد.
انتخاب بین پکیج `flag` استاندارد، Cobra، `urfave/cli`، یا `pflag` به پیچیدگی ابزار CLI شما بستگی دارد. برای ابزارهای ساده، `flag` کافی است. برای ابزارهای متوسط تا پیچیده با زیرفرمانها و نیاز به مدیریت پیکربندی، Cobra (به همراه Viper) یا `urfave/cli` گزینههای برتر هستند.
ورودی/خروجی و تعامل با کاربر
یک ابزار CLI تنها زمانی کارآمد است که بتواند به طور مؤثر با کاربر تعامل کند، ورودیها را دریافت کرده و خروجیهای واضح و مفید تولید کند. Go ابزارهای قدرتمندی برای مدیریت ورودی/خروجی (I/O) و بهبود تجربه کاربری فراهم میکند.
مدیریت ورودی/خروجی استاندارد (Standard I/O)
در Go، عملیات ورودی و خروجی با استفاده از پکیجهای `os` و `fmt` و دیگر پکیجهای `io` انجام میشود.
- `os.Stdout` و `fmt.Print/Println/Printf`: برای چاپ خروجی استاندارد (stdout) استفاده میشوند. `fmt.Println` رشته را چاپ کرده و یک خط جدید اضافه میکند، در حالی که `fmt.Printf` امکان فرمتبندی سفارشی را فراهم میآورد.
- `os.Stderr` و `fmt.Fprintln/Fprintf`: برای چاپ پیامهای خطا یا هشدار به خروجی خطای استاندارد (stderr) استفاده میشوند. استفاده از `stderr` برای خطاها یک رویه خوب است زیرا به کاربر امکان میدهد خروجی برنامه و پیامهای خطا را از یکدیگر جدا کند (مثلاً با ریدایرکت کردن).
- `os.Stdin` و `bufio.Scanner`: برای خواندن ورودی از کاربر (stdin) استفاده میشوند. `bufio.Scanner` یک راه کارآمد برای خواندن خط به خط یا کلمه به کلمه از ورودی است.
package main
import (
"bufio"
"fmt"
"os"
"strings"
)
func main() {
// چاپ به stdout
fmt.Println("این یک پیام عادی است.")
fmt.Printf("یک عدد: %d، یک رشته: %s\n", 123, "مثال")
// چاپ به stderr
fmt.Fprintln(os.Stderr, "این یک پیام خطاست!")
fmt.Fprintf(os.Stderr, "کد خطا: %d\n", 500)
// خواندن ورودی از کاربر
fmt.Print("نام خود را وارد کنید: ")
reader := bufio.NewReader(os.Stdin)
input, _ := reader.ReadString('\n')
name := strings.TrimSpace(input) // حذف کاراکترهای اضافی مانند newline
if name == "" {
fmt.Fprintln(os.Stderr, "خطا: نام نمیتواند خالی باشد.")
os.Exit(1)
}
fmt.Printf("سلام، %s! خوش آمدید.\n", name)
}
چاپ خروجی رنگی (Colored Output)
برای بهبود خوانایی خروجی CLI، به خصوص برای پیامهای هشدار، خطا یا تأیید، میتوانید از خروجی رنگی استفاده کنید. برای این کار، میتوانید به صورت دستی کدهای ANSI escape sequence را در رشتههای خود قرار دهید، اما استفاده از یک کتابخانه مانند `fatih/color` بسیار سادهتر و قابل حملتر است.
go get github.com/fatih/color
package main
import (
"fmt"
"github.com/fatih/color" // ایمپورت کتابخانه color
)
func main() {
// استفاده از توابع کمکی برای رنگها
color.Red("این یک پیام خطا قرمز است.")
color.Green("این یک پیام موفقیت سبز است.")
color.Yellow("این یک پیام هشدار زرد است.")
color.Cyan("این یک پیام اطلاعاتی آبی فیروزهای است.")
// استفاده از آبجکت Color برای سفارشیسازی بیشتر
boldRed := color.New(color.FgRed, color.Bold).PrintfFunc()
boldRed("این متن قرمز و پررنگ است: %s\n", "توجه!")
// ترکیب رنگها و پسزمینهها
highlight := color.New(color.BgYellow, color.FgBlack).SprintFunc()
fmt.Printf("این یک %s است.\n", highlight("برجسته کردن"))
}
نوارهای پیشرفت (Progress Bars)
برای وظایف طولانیمدت، یک نوار پیشرفت (progress bar) به کاربر اطلاع میدهد که برنامه هنوز در حال اجرا است و چقدر تا اتمام کار باقی مانده است. کتابخانههایی مانند `schollz/progressbar` یا `vbauerster/mpb` میتوانند این کار را ساده کنند.
go get github.com/schollz/progressbar/v3
package main
import (
"fmt"
"time"
progressbar "github.com/schollz/progressbar/v3"
)
func main() {
fmt.Println("شروع عملیات طولانی...")
bar := progressbar.Default(100) // 100 گام
for i := 0; i < 100; i++ {
bar.Add(1) // یک گام اضافه کنید
time.Sleep(20 * time.Millisecond) // شبیهسازی کار
}
fmt.Println("\nعملیات به پایان رسید.")
fmt.Println("\nمثال با نوار پیشرفت سفارشی:")
bar2 := progressbar.NewOptions(1000,
progressbar.OptionEnableColorCodes(true),
progressbar.OptionSetBytes(1000),
progressbar.OptionSetWidth(15),
progressbar.OptionSetDescription("[cyan][1/3][reset] Downloading the stuff..."),
progressbar.OptionSetTheme(progressbar.Theme{
Saucer: "[green]=[reset]",
SaucerPadding: " ",
BarStart: "|",
BarEnd: "|",
}))
for i := 0; i < 1000; i++ {
bar2.Add(1)
time.Sleep(10 * time.Millisecond)
}
fmt.Println("\nعملیات دوم به پایان رسید.")
}
دریافت ورودی تایید (Prompts and Confirmations)
گاهی نیاز است که کاربر یک پاسخ خاص (بله/خیر) یا ورودی مشخصی را ارائه دهد. کتابخانههایی مانند `manifoldco/promptui` یا `survey/survey` این فرآیند را تسهیل میکنند و تجربهای کاربرپسندتر از `bufio.Scanner` ارائه میدهند.
go get github.com/manifoldco/promptui
package main
import (
"fmt"
"os"
"github.com/manifoldco/promptui"
)
func main() {
// تاییدیه بله/خیر
prompt := promptui.Prompt{
Label: "آیا مایل به ادامه هستید؟",
IsConfirm: true, // نمایش (y/N)
}
result, err := prompt.Run()
if err != nil {
if err == promptui.ErrInterrupt {
fmt.Println("عملیات لغو شد.")
os.Exit(0)
}
fmt.Printf("Prompt failed %v\n", err)
return
}
if result == "y" {
fmt.Println("بسیار خوب! ادامه میدهیم.")
} else {
fmt.Println("عملیات متوقف شد.")
os.Exit(0)
}
// دریافت ورودی از کاربر با اعتبار سنجی
promptName := promptui.Prompt{
Label: "نام کاربری را وارد کنید",
Validate: func(input string) error {
if len(input) < 3 {
return fmt.Errorf("نام کاربری حداقل 3 کاراکتر باشد")
}
return nil
},
}
username, err := promptName.Run()
if err != nil {
fmt.Printf("Prompt failed %v\n", err)
return
}
fmt.Printf("نام کاربری شما: %s\n", username)
// انتخاب از بین گزینهها
items := []string{"Option A", "Option B", "Option C"}
promptSelect := promptui.Select{
Label: "یک گزینه را انتخاب کنید",
Items: items,
}
_, selectedItem, err := promptSelect.Run()
if err != nil {
fmt.Printf("Prompt failed %v\n", err)
return
}
fmt.Printf("شما انتخاب کردید: %s\n", selectedItem)
}
مدیریت سیگنالها (Handling Signals)
ابزارهای CLI باید بتوانند به سیگنالهای سیستم عامل (مانند Ctrl+C که SIGINT را ارسال میکند) به درستی پاسخ دهند تا منابع را آزاد کرده یا کارهای تمیزکاری را انجام دهند. پکیج `os/signal` برای این منظور استفاده میشود.
package main
import (
"fmt"
"os"
"os/signal"
"syscall"
"time"
)
func main() {
// ایجاد یک کانال برای دریافت سیگنالها
sigs := make(chan os.Signal, 1)
// ثبت سیگنالهای مورد نظر برای دریافت
signal.Notify(sigs, syscall.SIGINT, syscall.SIGTERM) // Ctrl+C و kill
done := make(chan bool, 1)
go func() {
sig := <-sigs // منتظر دریافت سیگنال باشید
fmt.Printf("\nدریافت سیگنال: %v\n", sig)
fmt.Println("در حال انجام کارهای تمیزکاری...")
// در اینجا میتوانید منابع را ببندید، فایلهای موقت را پاک کنید، و غیره.
time.Sleep(2 * time.Second) // شبیهسازی کار تمیزکاری
done <- true
}()
fmt.Println("برنامه در حال اجرا است. برای خروج Ctrl+C را فشار دهید...")
<-done // منتظر بمانید تا کارهای تمیزکاری انجام شود
fmt.Println("برنامه با موفقیت بسته شد.")
}
مدیریت صحیح ورودی/خروجی و تعامل با کاربر، به ابزار CLI شما امکان میدهد تا قدرتمند و در عین حال کاربرپسند باشد. با استفاده از پکیجهای استاندارد و کتابخانههای شخص ثالث مناسب، میتوانید تجربهای عالی را برای کاربران خود فراهم کنید.
ساختاردهی پروژه و بهترین شیوهها
یک ساختار پروژه خوب سازماندهی شده و پیروی از بهترین شیوهها، نگهداری، مقیاسپذیری و همکاری در توسعه ابزارهای Go CLI را آسانتر میکند. در این بخش، به برخی از این جنبهها میپردازیم.
طرحبندی پروژه (Project Layout)
در Go، هیچ ساختار پروژه "رسمی" و اجباری وجود ندارد، اما یک کانسنسوس جامعهای وجود دارد که طرحبندی استاندارد پروژه Go نامیده میشود. این طرحبندی به حفظ سازگاری و خوانایی بین پروژهها کمک میکند. برای یک ابزار CLI، ساختار زیر اغلب استفاده میشود:
mycli/
├── cmd/
│ └── mycli/ # نقطه ورود اصلی برنامه (main package)
│ └── main.go
│ └── subcommand1/ # اگر از Cobra استفاده میکنید، دستورات در اینجا
│ └── main.go (یا command.go)
│ └── subcommand2/
│ └── main.go (یا command.go)
├── pkg/ # کد کتابخانه عمومی که قرار است توسط برنامههای خارجی استفاده شود (اختیاری)
│ └── somepkg/
│ └── somepkg.go
├── internal/ # کد کتابخانه خصوصی که فقط برای این پروژه استفاده میشود
│ └── util/
│ └── util.go
│ └── config/
│ └── config.go
│ └── client/
│ └── client.go
├── test/ # فایلهای تست اضافی (مثلاً تستهای یکپارچهسازی)
├── vendor/ # وابستگیهای vendored (اگر از Go Modules استفاده نمیکنید یا برای بازتولیدپذیری بیشتر)
├── go.mod # فایل ماژول Go
├── go.sum # چکسامهای ماژول Go
├── Makefile # اسکریپتهای ساخت، تست و نصب
├── README.md # توضیحات پروژه
└── LICENSE # اطلاعات مجوز
- `cmd/`: شامل بستههای `main` برای ابزارهای اجرایی. هر زیردایرکتوری در `cmd` باید یک `main.go` (یا فایلهای دیگر) داشته باشد که نقطه ورود یک ابزار CLI جداگانه باشد. برای یک ابزار CLI واحد، معمولاً فقط `cmd/mycli/main.go` کافی است. اگر از Cobra استفاده میکنید، فایلهای مربوط به دستورات ریشه و زیرفرمانها نیز معمولاً در `cmd/` (مثلاً `cmd/root.go`, `cmd/greet.go`) قرار میگیرند.
- `pkg/`: کد کتابخانه که برای پروژههای خارجی نیز قابل استفاده است. اگر بخشی از کد CLI شما میتواند به عنوان یک کتابخانه مستقل توسط دیگران استفاده شود، آن را اینجا قرار دهید.
- `internal/`: کد کتابخانه که فقط برای استفاده داخلی این پروژه است و توسط پکیجهای خارجی قابل ایمپورت نیست. این یک مکان عالی برای قرار دادن منطق تجاری، سرویسها، مدلها، و کلاینتهای API است که خاص به ابزار CLI شما هستند. این کار کپسولهسازی را بهبود میبخشد.
- `go.mod` و `go.sum`: فایلهای مدیریت وابستگی Go Modules.
تست ابزارهای CLI
تستنویسی برای ابزارهای CLI بسیار مهم است تا اطمینان حاصل شود که ابزار شما به درستی عمل میکند. Go از تستنویسی بومی پشتیبانی میکند. میتوانید تستهای واحد (unit tests) و تستهای یکپارچهسازی (integration tests) بنویسید.
- تستهای واحد: توابع یا منطق خاصی را در پکیجهای `internal/` یا `pkg/` تست کنید.
- تستهای یکپارچهسازی: عملکرد کامل دستورات CLI را با ورودیهای مختلف و بررسی خروجیها و کدهای خروج تست کنید.
// internal/util/util.go
package util
import "fmt"
func GreetMessage(name string) string {
return fmt.Sprintf("Hello, %s!", name)
}
// internal/util/util_test.go
package util
import "testing"
func TestGreetMessage(t *testing.T) {
tests := []struct {
name string
want string
}{
{"World", "Hello, World!"},
{"Go", "Hello, Go!"},
{"", "Hello, !"},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
if got := GreetMessage(tt.name); got != tt.want {
t.Errorf("GreetMessage() = %q, want %q", got, tt.want)
}
})
}
}
برای تستهای یکپارچهسازی CLI، میتوانید برنامه خود را به عنوان یک فرایند جداگانه اجرا کرده و ورودی/خروجی آن را بررسی کنید:
// test/integration_test.go
package test
import (
"bytes"
"os"
"os/exec"
"strings"
"testing"
)
func TestCLIGreetCommand(t *testing.T) {
cmd := exec.Command("go", "run", "../main.go", "greet", "TestUser") // مسیر به main.go
var out bytes.Buffer
cmd.Stdout = &out
cmd.Stderr = os.Stderr // برای دیدن خطاهای exec در صورت وجود
err := cmd.Run()
if err != nil {
t.Fatalf("Command failed: %v, Output: %s", err, out.String())
}
expectedOutput := "سلام، TestUser!\n" // اطمینان از مطابقت با خروجی واقعی
if !strings.Contains(out.String(), expectedOutput) {
t.Errorf("Expected output to contain %q, got %q", expectedOutput, out.String())
}
}
میتوانید از ابزارهایی مانند `stretchr/testify` برای آسانتر کردن assertions در تستهای خود استفاده کنید.
استراتژیهای مدیریت خطا (Error Handling Strategies)
مدیریت خطا در Go یک موضوع مهم است. برای ابزارهای CLI، ارائه پیامهای خطای واضح و مفید به کاربر بسیار حیاتی است. بهترین شیوهها شامل:
- بازگرداندن خطاها به جای پانیک: به جای استفاده از `panic` برای خطاهای قابل بازیابی، خطا را بازگردانید تا تابع فراخواننده آن را مدیریت کند.
- پیچیدن خطاها (Error Wrapping): از `fmt.Errorf` با `%w` برای پیچیدن خطاها استفاده کنید. این کار به شما امکان میدهد تا یک زنجیره از خطاها را ردیابی کنید و اطلاعات بیشتری را در مورد علت اصلی خطا به دست آورید.
// Go 1.13+ if err != nil { return fmt.Errorf("خطا در خواندن فایل پیکربندی: %w", err) } // در جای دیگر: errors.Is(err, os.ErrNotExist) یا errors.As(err, &MySpecificError)
- خطاهای سنتینل (Sentinel Errors): برای خطاهای خاص و مورد انتظار، متغیرهای خطای سراسری تعریف کنید (مثلاً `var ErrNotFound = errors.New("مورد یافت نشد")`). این کار امکان بررسی نوع خطا را با `errors.Is` فراهم میکند.
- خروجی خطای واضح: پیامهای خطا را به `os.Stderr` ارسال کنید و با کد خروج غیر صفر از برنامه خارج شوید (`os.Exit(1)`).
- پشته تماس (Stack Traces): در محیطهای توسعه یا برای خطاهای غیرمنتظره، میتوانید پشته تماس را با استفاده از کتابخانههایی مانند `pkg/errors` یا با فعال کردن آپشنهای خاص در لاگها، چاپ کنید.
لاگنویسی (Logging)
برای دیباگ کردن و پایش ابزارهای CLI، لاگنویسی مؤثر ضروری است. Go دارای پکیج `log` استاندارد است، اما برای نیازهای پیشرفتهتر، کتابخانههایی مانند `zap` (توسط Uber) یا `logrus` بسیار محبوب هستند.
- `log` استاندارد: ساده و کافی برای بسیاری از موارد.
import "log" log.Println("پیام اطلاعاتی") log.Printf("یک خطا رخ داد: %s", err)
- `logrus` یا `zap`: ارائه سطوح لاگ (Info, Warn, Error, Debug)، خروجی JSON، فیلدهای ساختاریافته، و عملکرد بالاتر. برای ابزارهای پیچیدهتر که نیاز به لاگهای قابل تحلیل دارند، توصیه میشوند.
مثال با `logrus`:
go get github.com/sirupsen/logrus
package main
import (
"os"
"github.com/sirupsen/logrus"
)
var log = logrus.New()
func init() {
log.Out = os.Stdout // خروجی به stdout
log.Formatter = &logrus.TextFormatter{
DisableColors: false, // رنگی کردن خروجی
FullTimestamp: true,
}
log.SetLevel(logrus.InfoLevel) // سطح پیشفرض لاگ
}
func main() {
log.Info("برنامه CLI شروع به کار کرد.")
log.Debug("این یک پیام دیباگ است.") // نمایش داده نمیشود مگر سطح لاگ را تغییر دهید
log.Warn("فایل پیکربندی پیدا نشد، از مقادیر پیشفرض استفاده میشود.")
err := someFunctionThatMightFail()
if err != nil {
log.WithError(err).Error("خطا در اجرای تابع.")
// log.WithField("user_id", 123).Error("خطا در پردازش کاربر.")
}
log.Info("برنامه CLI با موفقیت به پایان رسید.")
}
func someFunctionThatMightFail() error {
// شبیهسازی خطا
return fmt.Errorf("چیزی اشتباه پیش رفت")
}
با تنظیم `log.SetLevel(logrus.DebugLevel)` در `init()`، میتوانید پیامهای دیباگ را نیز مشاهده کنید.
مدیریت ماژولهای Go (Go Modules)
از Go 1.11 به بعد، Go Modules به عنوان راه استاندارد برای مدیریت وابستگیها معرفی شد. این سیستم وابستگیهای شما را ردیابی میکند و ساختهای قابل تکرار را تضمین میکند.
برای شروع یک ماژول جدید:
go mod init your_module_name
با اجرای دستوراتی مانند `go build` یا `go run`، Go به طور خودکار وابستگیهای لازم را دانلود و به فایل `go.mod` اضافه میکند. `go mod tidy` نیز وابستگیهای استفاده نشده را حذف و `go.sum` را بهروز میکند.
go mod tidy
پیروی از این بهترین شیوهها به شما کمک میکند تا ابزارهای CLI در Go را به صورت سازماندهی شده، قابل تست، قابل نگهداری و حرفهای توسعه دهید.
پیادهسازی ویژگیهای پیشرفته در ابزارهای CLI
یک ابزار CLI خوب تنها به مدیریت آرگومانها محدود نمیشود، بلکه شامل ویژگیهای پیشرفتهای است که آن را قدرتمندتر و انعطافپذیرتر میکند. در این بخش، به برخی از این ویژگیها و نحوه پیادهسازی آنها در Go میپردازیم.
همزمانی برای وظایف طولانیمدت (Concurrency for Long-Running Tasks)
Go با Goroutineها و Channelها، همزمانی را به یک ویژگی برجسته تبدیل کرده است. این قابلیت به ویژه برای ابزارهای CLI که نیاز به انجام عملیات موازی (مانند دانلود همزمان فایلها، پردازش چندین آیتم، یا فراخوانی چندین API) دارند، بسیار مفید است.
مثال: دانلود همزمان چندین URL
package main
import (
"fmt"
"io/ioutil"
"net/http"
"sync"
"time"
)
func main() {
urls := []string{
"https://example.com",
"https://go.dev",
"https://github.com",
"https://bing.com",
}
var wg sync.WaitGroup // WaitGroup برای منتظر ماندن برای اتمام همه Goroutineها
results := make(chan string, len(urls)) // کانال برای جمعآوری نتایج
fmt.Println("شروع دانلود همزمان...")
for _, url := range urls {
wg.Add(1) // هر Goroutine یک کار اضافه میکند
go func(u string) {
defer wg.Done() // اطمینان از کاهش شمارنده پس از اتمام Goroutine
resp, err := http.Get(u)
if err != nil {
results <- fmt.Sprintf("خطا در دانلود %s: %v", u, err)
return
}
defer resp.Body.Close()
body, err := ioutil.ReadAll(resp.Body)
if err != nil {
results <- fmt.Sprintf("خطا در خواندن پاسخ %s: %v", u, err)
return
}
results <- fmt.Sprintf("دانلود %s با حجم %d بایت انجام شد.", u, len(body))
}(url) // پاس دادن url به Goroutine
}
// یک Goroutine جداگانه برای بستن کانال پس از اتمام همه کارها
go func() {
wg.Wait() // منتظر اتمام همه Goroutineها بمانید
close(results) // کانال را ببندید تا حلقه for-range بتواند خاتمه یابد
}()
// خواندن نتایج از کانال
for res := range results {
fmt.Println(res)
}
fmt.Println("همه دانلودها به پایان رسید.")
time.Sleep(1 * time.Second) // صبر برای اطمینان از چاپ همه خروجیها
}
مدیریت پیکربندی خارجی (External Configuration Management)
همانطور که قبلاً اشاره شد، Viper یک کتابخانه عالی برای مدیریت پیکربندی است. استفاده از آن فراتر از تنظیم مقادیر پیشفرض است؛ میتواند از چندین منبع پیکربندی کند، مانند:
- فایلهای پیکربندی: JSON, YAML, TOML.
- متغیرهای محیطی: معمولاً با یک پیشوند خاص (`MYAPP_ENV_VAR`).
- فلگهای خط فرمان: (ادغام با Cobra/pflag).
- سیستمهای کلید-مقدار راه دور: مانند Consul، Etcd، Zookeeper.
Viper اولویتبندی دارد (فلگها > متغیرهای محیطی > فایل پیکربندی > مقادیر پیشفرض)، که انعطافپذیری زیادی را فراهم میکند.
یکپارچهسازی پایگاه داده (Database Integration)
بسیاری از ابزارهای CLI نیاز به تعامل با پایگاه داده دارند، مثلاً برای مهاجرت دادهها، گزارشگیری یا مدیریت کاربران. Go دارای پکیج استاندارد `database/sql` و درایورهای مختلف برای پایگاههای داده محبوب (PostgreSQL, MySQL, SQLite, SQL Server) است.
// مثال اتصال به SQLite و ایجاد یک جدول
package main
import (
"database/sql"
"fmt"
"log"
_ "github.com/mattn/go-sqlite3" // درایور SQLite
)
func main() {
db, err := sql.Open("sqlite3", "./test.db")
if err != nil {
log.Fatal(err)
}
defer db.Close()
sqlStmt := `
create table if not exists users (id integer not null primary key autoincrement, name text);
delete from users;
`
_, err = db.Exec(sqlStmt)
if err != nil {
log.Printf("%q: %s\n", err, sqlStmt)
return
}
tx, err := db.Begin()
if err != nil {
log.Fatal(err)
}
stmt, err := tx.Prepare("insert into users(name) values(?)")
if err != nil {
log.Fatal(err)
}
defer stmt.Close()
for i := 0; i < 10; i++ {
_, err = stmt.Exec(fmt.Sprintf("کاربر شماره %03d", i))
if err != nil {
log.Fatal(err)
}
}
tx.Commit()
rows, err := db.Query("select id, name from users")
if err != nil {
log.Fatal(err)
}
defer rows.Close()
for rows.Next() {
var id int
var name string
err = rows.Scan(&id, &name)
if err != nil {
log.Fatal(err)
}
fmt.Println(id, name)
}
err = rows.Err()
if err != nil {
log.Fatal(err)
}
}
برای ORMها (Object-Relational Mappers)، کتابخانههایی مانند `GORM` یا `sqlx` میتوانند توسعه را سادهتر کنند.
ارتباط با APIها (API Communication)
بسیاری از ابزارهای CLI با سرویسهای ابری یا APIهای RESTful تعامل دارند. پکیج `net/http` Go یک کلاینت HTTP قدرتمند و بومی را فراهم میکند.
package main
import (
"encoding/json"
"fmt"
"net/http"
"time"
)
type Post struct {
UserID int `json:"userId"`
ID int `json:"id"`
Title string `json:"title"`
Body string `json:"body"`
}
func main() {
client := http.Client{
Timeout: 10 * time.Second,
}
resp, err := client.Get("https://jsonplaceholder.typicode.com/posts/1")
if err != nil {
fmt.Printf("خطا در فراخوانی API: %v\n", err)
return
}
defer resp.Body.Close()
if resp.StatusCode != http.StatusOK {
fmt.Printf("خطا در پاسخ API: وضعیت %d\n", resp.StatusCode)
return
}
var post Post
if err := json.NewDecoder(resp.Body).Decode(&post); err != nil {
fmt.Printf("خطا در دیکد کردن JSON: %v\n", err)
return
}
fmt.Printf("عنوان پست: %s\n", post.Title)
fmt.Printf("محتوای پست:\n%s\n", post.Body)
}
برای RESTful clientهای قویتر یا SDKهای API، میتوانید از کتابخانههای شخص ثالث یا کد تولید شده استفاده کنید.
کار با فایل سیستم (Working with Filesystems)
ابزارهای CLI اغلب نیاز به خواندن، نوشتن یا دستکاری فایلها و دایرکتوریها دارند. پکیجهای `os` و `io/ioutil` (که در Go 1.16 به `io` منتقل شد) ابزارهای لازم را فراهم میکنند.
package main
import (
"fmt"
"io/ioutil"
"os"
"path/filepath"
)
func main() {
// نوشتن به یک فایل
data := []byte("این یک متن نمونه است.\nخط دوم.\n")
err := ioutil.WriteFile("output.txt", data, 0644) // 0644 مجوز فایل
if err != nil {
fmt.Printf("خطا در نوشتن فایل: %v\n", err)
return
}
fmt.Println("فایل output.txt با موفقیت نوشته شد.")
// خواندن از یک فایل
readData, err := ioutil.ReadFile("output.txt")
if err != nil {
fmt.Printf("خطا در خواندن فایل: %v\n", err)
return
}
fmt.Printf("محتوای فایل output.txt:\n%s", string(readData))
// ایجاد دایرکتوری
newDir := "temp_dir"
err = os.Mkdir(newDir, 0755)
if err != nil {
fmt.Printf("خطا در ایجاد دایرکتوری: %v\n", err)
// return // ممکن است دایرکتوری از قبل وجود داشته باشد
} else {
fmt.Printf("دایرکتوری %s با موفقیت ایجاد شد.\n", newDir)
}
// پیمایش دایرکتوریها
fmt.Println("\nفایلهای در دایرکتوری جاری:")
err = filepath.Walk(".", func(path string, info os.FileInfo, err error) error {
if err != nil {
return err
}
if !info.IsDir() {
fmt.Println("-", path)
}
return nil
})
if err != nil {
fmt.Printf("خطا در پیمایش: %v\n", err)
}
// حذف فایل یا دایرکتوری (فقط برای تست، در کد واقعی با احتیاط استفاده شود)
// err = os.Remove("output.txt")
// err = os.RemoveAll(newDir)
}
ملاحظات امنیتی (Security Considerations)
برای ابزارهای CLI که با اطلاعات حساس (مانند توکنها، رمزهای عبور) کار میکنند، ملاحظات امنیتی حیاتی هستند:
- ورودی حساس: هرگز اطلاعات حساس را به صورت ساده در آرگومانهای خط فرمان نگیرید، زیرا ممکن است در تاریخچه Bash یا لاگهای سیستم ظاهر شوند. به جای آن، از ورودی پنهان (بدون echo) استفاده کنید (مثلاً با `syscall.ReadPassword` یا کتابخانههایی مانند `go-password/input`).
- پیکربندی: اطلاعات حساس را در فایلهای پیکربندی با مجوزهای محدود (مثلاً 0600) ذخیره کنید. در صورت امکان، از متغیرهای محیطی امن یا سیستمهای مدیریت رمز عبور (مانند Vault) استفاده کنید.
- بررسی ورودی: همیشه ورودیهای کاربر را بررسی و پاکسازی کنید تا از حملات Injection (مانند Shell Injection) جلوگیری شود.
- مجوزها: ابزار را با حداقل مجوزهای لازم اجرا کنید.
ابزارهای CLI خود بهروز شونده (Self-Updating CLI Tools)
برای بهبود تجربه کاربری و اطمینان از اینکه کاربران همیشه آخرین نسخه ابزار شما را دارند، میتوانید قابلیت خود بهروزرسانی (self-update) را اضافه کنید. کتابخانههایی مانند `rhysd/go-github-selfupdate` یا `inconshreveable/go-update` میتوانند این کار را تسهیل کنند. این قابلیت معمولاً شامل بررسی نسخههای جدید از یک مخزن گیتهاب و دانلود و جایگزینی باینری فعلی است.
با پیادهسازی این ویژگیهای پیشرفته، میتوانید ابزارهای CLI Go را ایجاد کنید که نه تنها وظایف خود را به طور کارآمد انجام میدهند، بلکه تجربه کاربری غنی و امنی را نیز ارائه میدهند.
استقرار و توزیع ابزارهای Go CLI
یکی از بزرگترین مزایای Go برای ابزارهای CLI، سهولت استقرار و توزیع است. باینریهای مستقل و قابلیت کامپایل متقاطع (cross-compilation) Go را به انتخابی عالی برای ساخت ابزارهای قابل حمل تبدیل میکند.
کامپایل و ساخت باینریها (`go build`)
دستور `go build` هسته اصلی فرایند ساخت است. بدون هیچ آرگومانی، این دستور فایلهای منبع را کامپایل کرده و یک فایل اجرایی با نام دایرکتوری جاری (یا نام پکیج `main` در `go.mod`) ایجاد میکند.
go build
این دستور فایل اجرایی را در دایرکتوری جاری ایجاد میکند. برای تعیین نام خروجی و محل آن، از فلگ `-o` استفاده کنید:
go build -o mycli-tool main.go
برای حذف اطلاعات دیباگ از باینری و کاهش اندازه آن، از فلگهای `ldflags` استفاده کنید:
go build -ldflags "-s -w" -o mycli-tool main.go
- `-s`: Symbol table را حذف میکند.
- `-w`: DWARF debugging information را حذف میکند.
این کار میتواند اندازه باینری را به طور قابل توجهی کاهش دهد، به خصوص برای باینریهای مستقل Go.
کامپایل متقاطع (Cross-Compilation)
همانطور که قبلاً ذکر شد، Go به طور بومی از کامپایل متقاطع پشتیبانی میکند. این به این معنی است که میتوانید باینریهایی را برای سیستمعاملها (`GOOS`) و معماریهای مختلف (`GOARCH`) از یک سیستم واحد کامپایل کنید.
مثالها:
- برای Linux (64-bit AMD):
GOOS=linux GOARCH=amd64 go build -o bin/mycli-linux-amd64 main.go
- برای macOS (64-bit Intel):
GOOS=darwin GOARCH=amd64 go build -o bin/mycli-darwin-amd64 main.go
- برای macOS (Apple Silicon/ARM64):
GOOS=darwin GOARCH=arm64 go build -o bin/mycli-darwin-arm64 main.go
- برای Windows (64-bit):
GOOS=windows GOARCH=amd64 go build -o bin/mycli-windows-amd64.exe main.go
- برای Raspberry Pi (ARMv7):
GOOS=linux GOARCH=arm GOARM=7 go build -o bin/mycli-linux-armv7 main.go
میتوانید یک اسکریپت `Makefile` یا `build.sh` برای خودکارسازی این فرآیندها ایجاد کنید.
توزیع باینریها
GitHub Releases
یکی از سادهترین و متداولترین روشها برای توزیع ابزارهای Go CLI، استفاده از GitHub Releases است. میتوانید باینریهای کامپایل شده برای پلتفرمهای مختلف را به عنوان دارایی (assets) به یک انتشار (release) در گیتهاب پیوست کنید. کاربران سپس میتوانند باینری مناسب سیستم خود را دانلود کنند.
ابزارهایی مانند `goreleaser` (که در ادامه توضیح داده میشود) میتوانند این فرآیند را به طور کامل خودکار کنند.
`go install`
اگر پروژه شما به عنوان یک ماژول Go در یک مخزن قابل دسترسی (مانند گیتهاب) منتشر شده است، کاربران میتوانند ابزار شما را مستقیماً با `go install` نصب کنند. این دستور باینری را کامپایل و در `$GOPATH/bin` یا `$GOBIN` قرار میدهد (که باید در PATH کاربر باشد).
go install github.com/your-username/your-repo/cmd/mycli@latest
این روش برای توسعهدهندگان Go که ابزارهای شما را میشناسند و نصب Go روی سیستم خود دارند، بسیار راحت است.
Homebrew Taps (برای macOS/Linux)
Homebrew یک مدیریت بسته محبوب برای macOS و Linux است. میتوانید یک "Tap" (یک مخزن گیتهاب که فرمولهای Homebrew را نگهداری میکند) برای ابزار خود ایجاد کنید. این کار به کاربران امکان میدهد با یک دستور ساده ابزار شما را نصب کنند:
brew tap your-username/tap
brew install your-cli-tool
`goreleaser` میتواند به صورت خودکار فرمولهای Homebrew و Tap را برای شما ایجاد کند.
Containerization (Docker)
برای ابزارهای CLI که قرار است در محیطهای ایزوله یا به عنوان بخشی از یک جریان کاری خودکار (مثلاً در CI/CD) اجرا شوند، داکرسازی (containerization) یک گزینه عالی است. میتوانید یک Dockerfile ایجاد کنید که ابزار Go شما را کامپایل کرده و آن را درون یک ایمیج داکر بسیار کوچک قرار دهد (مثلاً با استفاده از ایمیجهای `scratch` یا `alpine`).
# Dockerfile
FROM golang:1.21-alpine AS builder
WORKDIR /app
COPY go.mod go.sum ./
RUN go mod download
COPY . .
RUN go build -ldflags "-s -w" -o mycli ./cmd/mycli # فرض بر این است که main در cmd/mycli است
FROM alpine/git AS runtime # یا alpine:latest یا scratch
WORKDIR /usr/local/bin
COPY --from=builder /app/mycli .
ENTRYPOINT ["mycli"]
CMD ["--help"]
سپس میتوانید ایمیج داکر را بسازید و آن را به Docker Hub یا یک رجیستری کانتینر دیگر پوش کنید.
docker build -t mycli-tool .
docker run mycli-tool --version
مدیریت بستههای سیستمعامل (Linux .deb/.rpm)
برای توزیع گستردهتر در سیستمعاملهای لینوکس، میتوانید بستههای `.deb` (برای Debian/Ubuntu) یا `.rpm` (برای Fedora/CentOS) ایجاد کنید. این کار به طور معمول توسط ابزارهای شخص ثالث مانند `goreleaser` یا `nfpm` انجام میشود.
خودکارسازی انتشار با Goreleaser
`goreleaser` یک ابزار محبوب و قدرتمند است که کل فرآیند انتشار ابزارهای Go CLI را خودکار میکند. این شامل:
- کامپایل متقاطع برای پلتفرمهای مختلف.
- ساخت باینریهای مستقل.
- امضای باینریها (code signing).
- ایجاد آرشیوهای فشرده (zip/tar.gz).
- آپلود به GitHub Releases.
- آپدیت Homebrew Tap.
- ساخت ایمیجهای داکر.
- ساخت بستههای `.deb`/`.rpm`.
تنها کاری که باید انجام دهید، پیکربندی `goreleaser` در فایل `.goreleaser.yaml` و اجرای آن است.
# .goreleaser.yaml (مثال بسیار ساده)
builds:
- env:
- CGO_ENABLED=0
goos:
- linux
- windows
- darwin
goarch:
- amd64
- arm64
ldflags:
- -s -w
archives:
- format: tar.gz
# other archive options
checksum:
name_template: 'checksums.txt'
snaps:
- # snapcraft options
release:
github:
owner: your-github-username
name: your-repo-name
name_template: "{{ .Tag }}"
changelog:
sort: asc
filters:
exclude:
- '^docs:'
- '^test:'
brews:
- name: your-cli-tool # Homebrew formula name
tap:
owner: your-github-username
name: homebrew-tap # Your Homebrew tap repo name
homepage: "https://your-homepage.com"
description: "A description for your CLI tool"
install: |
bin.install "your-cli-tool"
با `goreleaser`, میتوانید فرآیند CI/CD (Continuous Integration/Continuous Delivery) خود را برای انتشار ابزارهای CLI Go به طور کامل خودکار کنید، و اطمینان حاصل کنید که کاربران شما همیشه به آخرین و پایدارترین نسخهها دسترسی دارند.
امضای کد (Code Signing)
برای اطمینان از صحت و اصالت باینریهای توزیع شده، به خصوص در محیطهای تولیدی، امضای کد (code signing) یک مرحله مهم است. این کار به کاربران کمک میکند تا تأیید کنند که باینری توسط شما منتشر شده و دستکاری نشده است. در macOS، امضا برای جلوگیری از هشدارهای امنیتی Gatekeeper الزامی است. `goreleaser` و ابزارهای مشابه از امضای کد پشتیبانی میکنند.
با در نظر گرفتن این استراتژیهای استقرار و توزیع، میتوانید ابزار CLI Go خود را به راحتی در دسترس کاربران قرار داده و فرآیند انتشار را برای خود و تیمتان بهینه کنید.
نتیجهگیری
توسعه ابزارهای خط فرمان با Go یک تجربه قدرتمند و لذتبخش است. همانطور که در این مقاله به تفصیل بررسی شد، Go با ویژگیهای ذاتی خود مانند کامپایل به باینریهای مستقل، پشتیبانی عالی از همزمانی، عملکرد بالا، و کتابخانه استاندارد غنی، به انتخابی بینظیر برای این منظور تبدیل شده است. قابلیت کامپایل متقاطع به توسعهدهندگان این امکان را میدهد که به راحتی ابزارهای خود را برای انواع سیستمعاملها و معماریها منتشر کنند، بدون اینکه نگران وابستگیهای runtime باشند.
ما از مبانی مدیریت آرگومانها و فلگها با پکیج `flag` استاندارد Go شروع کردیم و سپس به دنیای قدرتمند کتابخانههای شخص ثالث مانند Cobra و Viper قدم گذاشتیم. این ابزارها امکان ساخت CLIهای پیچیده با زیرفرمانهای متعدد و مدیریت پیکربندی انعطافپذیر را فراهم میکنند. همچنین، به بررسی روشهای بهبود تعامل با کاربر از طریق خروجیهای رنگی، نوارهای پیشرفت و دریافت ورودیهای تأیید پرداختیم.
در ادامه، اهمیت ساختاردهی صحیح پروژه، تستنویسی دقیق، مدیریت خطای مؤثر و لاگنویسی برای نگهداری و مقیاسپذیری ابزارهای CLI در Go مورد تأکید قرار گرفت. این بهترین شیوهها به شما کمک میکنند تا کدی تمیز، قابل اعتماد و قابل همکاری تولید کنید.
در نهایت، به جنبههای پیشرفتهتر مانند همزمانی برای وظایف سنگین، یکپارچهسازی پایگاه داده و APIها، کار با فایل سیستم و ملاحظات امنیتی پرداختیم. این ویژگیها به شما امکان میدهند تا ابزارهای CLI با قابلیتهای گستردهای بسازید که از پس چالشهای دنیای واقعی برآیند. بخش استقرار و توزیع، فرآیند رساندن ابزار به دست کاربران نهایی را با استفاده از GitHub Releases، `go install`، Homebrew، Docker و خودکارسازی با `goreleaser` سادهسازی کرد.
چه در حال ساخت یک ابزار ساده برای خودکارسازی یک وظیفه کوچک باشید، چه یک ابزار پیچیده برای مدیریت زیرساختهای ابری، Go ابزارها و قابلیتهای لازم را برای موفقیت شما فراهم میکند. با دانش و بینشهایی که از این مقاله به دست آوردهاید، اکنون مجهز هستید تا شروع به ساخت ابزارهای خط فرمان Go خود کنید و بهرهوری خود و تیمتان را افزایش دهید. دنیای ابزارهای CLI Go در انتظار نوآوریهای شماست!
“تسلط به برنامهنویسی پایتون با هوش مصنوعی: آموزش کدنویسی هوشمند با ChatGPT”
"تسلط به برنامهنویسی پایتون با هوش مصنوعی: آموزش کدنویسی هوشمند با ChatGPT"
"با شرکت در این دوره جامع و کاربردی، به راحتی مهارتهای برنامهنویسی پایتون را از سطح مبتدی تا پیشرفته با کمک هوش مصنوعی ChatGPT بیاموزید. این دوره، با بیش از 6 ساعت محتوای آموزشی، شما را قادر میسازد تا به سرعت الگوریتمهای پیچیده را درک کرده و اپلیکیشنهای هوشمند ایجاد کنید. مناسب برای تمامی سطوح با زیرنویس فارسی حرفهای و امکان دانلود و تماشای آنلاین."
ویژگیهای کلیدی:
بدون نیاز به تجربه قبلی برنامهنویسی
زیرنویس فارسی با ترجمه حرفهای
۳۰ ٪ تخفیف ویژه برای دانشجویان و دانش آموزان