แพ็คเกจนี้เป็น wrapper เล็กๆ แต่มีประโยชน์มากรอบๆ 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
ตรวจพบการแข่งขัน) และเอกสารบอกว่ามันผิด:
ดังนั้นจึงไม่ถูกต้องที่จะเรียก Wait ก่อนที่การอ่านทั้งหมดจากไปป์จะเสร็จสิ้น ด้วยเหตุผลเดียวกัน การเรียก Run เมื่อใช้ StdoutPipe ไม่ถูกต้อง
วิธีแก้ปัญหาที่เหมาะสมคือการตั้งค่า io.Writer
ของ Stdout
เพื่อให้เธรดปลอดภัยและไม่เชื้อชาติ จำเป็นต้องมีการทำงานเพิ่มเติมในการเขียนในขณะที่อาจมี goroutines จำนวน N-จำนวนมากที่อ่านได้ 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 แต่โดยปกติแล้วเราต้องฆ่า ID กลุ่มกระบวนการแทน go-cmd/Cmd.Stop
ใช้เวทมนตร์ระดับต่ำที่จำเป็นเพื่อทำให้สิ่งนี้เกิดขึ้น
นอกเหนือจากความครอบคลุมการทดสอบ 100% และไม่มีสภาวะการแข่งขันแล้ว แพ็คเกจนี้ยังถูกใช้งานในสภาพแวดล้อมการใช้งานจริงอีกด้วย
Brian Ip เขียนโค้ดต้นฉบับเพื่อรับสถานะการออก น่าแปลกที่ Go ไม่ได้จัดเตรียมสิ่งนี้ไว้เท่านั้น แต่ยังต้องใช้เวทย์มนตร์เช่น exiterr.Sys().(syscall.WaitStatus)
และอื่นๆ อีกมากมาย
MIT © go-Cmd.