Этот пакет представляет собой небольшую, но очень полезную оболочку 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
требуется все, что уже есть в этом пакете.
Обычно требуется прочитать стандартный вывод или стандартный поток ошибок во время выполнения команды. Общий подход заключается в вызове StdoutPipe и чтении из предоставленного io.ReadCloser
. Это работает, но это неправильно, потому что это вызывает состояние гонки (которое обнаруживает go test -race
), и документы говорят, что это неправильно:
Таким образом, неправильно вызывать Wait до завершения всех операций чтения из канала. По той же причине неправильно вызывать Run при использовании StdoutPipe.
Правильное решение — установить io.Writer
для Stdout
. Чтобы обеспечить потокобезопасность и отсутствие гонок, для записи требуется дополнительная работа, в то время как, возможно, читается N-множество горутин. go-cmd/Cmd
выполнил эту работу.
Как и в случае со стандартным выводом и стандартным выводом в реальном времени, приятно видеть, например, затраченное время выполнения. Этот пакет позволяет: Status
может быть вызван в любое время любой горутиной и возвращает следующую структуру:
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% покрытия тестами и отсутствия гонок, этот пакет активно используется в производственных средах.
Брайан Ип написал исходный код для получения статуса выхода. Как ни странно, Go не просто предоставляет это, он требует магии типа exiterr.Sys().(syscall.WaitStatus)
и многого другого.
MIT © go-Cmd.