هذه الحزمة عبارة عن غلاف صغير ولكنه مفيد جدًا حول os/exec.Cmd مما يجعل تشغيل الأوامر الخارجية في تطبيقات متزامنة للغاية وغير متزامنة وفي الوقت الفعلي أمرًا آمنًا وبسيطًا. وهو يعمل على Linux وmacOS وWindows. إليك الاستخدام الأساسي:
import (
"fmt"
"time"
"github.com/go-cmd/cmd"
)
func main () {
// Start a long-running process, capture stdout and stderr
findCmd := cmd . NewCmd ( "find" , "/" , "--name" , "needle" )
statusChan := findCmd . Start () // non-blocking
ticker := time . NewTicker ( 2 * time . Second )
// Print last line of stdout every 2s
go func () {
for range ticker . C {
status := findCmd . Status ()
n := len ( status . Stdout )
fmt . Println ( status . Stdout [ n - 1 ])
}
}()
// Stop command after 1 hour
go func () {
<- time . After ( 1 * time . Hour )
findCmd . Stop ()
}()
// Check if command is done
select {
case finalStatus := <- statusChan :
// done
default :
// no, still running
}
// Block waiting for command to exit, be stopped, or be killed
finalStatus := <- statusChan
}
هذا كل شيء، ثلاث طرق فقط: Start
Stop
Status
. عندما يكون ذلك ممكنًا، من الأفضل استخدام go-cmd/Cmd
بدلاً من os/exec.Cmd
لأن go-cmd/Cmd
يوفر ما يلي:
كما يوضح المثال أعلاه، يؤدي بدء الأمر على الفور إلى إرجاع القناة التي يتم إرسال الحالة النهائية إليها عند خروج الأمر لأي سبب. لذا، تعمل الأوامر بشكل افتراضي بشكل غير متزامن، ولكن التشغيل بشكل متزامن ممكن وسهل أيضًا:
// Run foo and block waiting for it to exit
c := cmd . NewCmd ( "foo" )
s := <- c . Start ()
لتحقيق نفس الشيء مع os/exec.Cmd
يتطلب كل ما تفعله هذه الحزمة بالفعل.
من الشائع أن ترغب في قراءة stdout أو stderr أثناء تشغيل الأمر. الطريقة الشائعة هي الاتصال بـ StdoutPipe والقراءة من الملف io.ReadCloser
المتوفر. يعمل هذا ولكنه خطأ لأنه يسبب حالة سباق (التي يتم اكتشافها go test -race
) وتقول المستندات إنها خاطئة:
ولذلك فمن غير الصحيح استدعاء الانتظار قبل اكتمال كافة عمليات القراءة من توجيه الإخراج. لنفس السبب، من غير الصحيح استدعاء Run عند استخدام StdoutPipe.
الحل المناسب هو تعيين io.Writer
لـ Stdout
. لكي تكون آمنًا وغير متعرجًا، يتطلب هذا مزيدًا من العمل للكتابة أثناء قراءة N-many goroutines. لقد قام go-cmd/Cmd
بهذا العمل.
على غرار stdout وstderr في الوقت الفعلي، من الجيد أن نرى، على سبيل المثال، وقت التشغيل المنقضي. تسمح هذه الحزمة بما يلي: يمكن استدعاء Status
في أي وقت بواسطة أي goroutine، وتقوم بإرجاع هذه البنية:
type Status struct {
Cmd string
PID int
Complete bool
Exit int
Error error
Runtime float64 // seconds
Stdout [] string
Stderr [] string
}
بالحديث عن تلك البنية أعلاه، فإن Go المدمج في Cmd
لا يضع جميع معلومات الإرجاع في مكان واحد، وهو أمر جيد لأن Go رائع! ولكن لتوفير بعض الوقت، يستخدم go-cmd/Cmd
بنية Status
أعلاه لنقل جميع المعلومات حول الأمر. حتى عند انتهاء الأمر، يؤدي استدعاء Status
إلى إرجاع الحالة النهائية، وهي نفس الحالة النهائية المرسلة إلى قناة الحالة التي يتم إرجاعها عن طريق استدعاء Start
.
يمكن حظر os/exec/Cmd.Wait حتى بعد إنهاء الأمر. يمكن أن يكون ذلك مفاجئًا ويسبب مشاكل. لكن go-cmd/Cmd.Stop
ينهي الأمر بشكل موثوق، ولا توجد مفاجآت. تتعلق المشكلة بمعرفات مجموعة العمليات. من الشائع إيقاف الأمر PID، ولكن عادةً ما يتعين عليك إيقاف معرف مجموعة العمليات الخاص به بدلاً من ذلك. ينفذ go-cmd/Cmd.Stop
السحر منخفض المستوى الضروري لتحقيق ذلك.
بالإضافة إلى تغطية الاختبار بنسبة 100% وعدم وجود ظروف سباق، يتم استخدام هذه الحزمة بشكل نشط في بيئات الإنتاج.
كتب Brian Ip الكود الأصلي للحصول على حالة الخروج. الغريب أن Go لا يوفر هذا فحسب، بل يتطلب سحرًا مثل exiterr.Sys().(syscall.WaitStatus)
والمزيد.
معهد ماساتشوستس للتكنولوجيا © go-Cmd.