Imagine you start a long-running process. It may take several minutes to complete. The process provides output (stdout/stderr), that you can analyse, and provide your user some info/report on the process’ progress (eg. how much job has been already done). You want (1) to monitor the process, and to provide such progress status, and (2) detect when the process has been finished (in any way: it’s no longer active). That’s all I want/need to do.
I made a simple experiment. I wrote the following Bash script:
for i in `seq 10`; do
sleep 5
echo $i
done
It’s a simple process, that counts up to 10, with some delay before counting up each number. It simulates a long-running process, which will be completed without any interruptions.
Now, I wanted a Go program, that:
(1) starts the process (the Bash script),
(2) reports the last number counted,
(3) detects, that the started process has been completed.
And all of this, without any blocking, since reporting can be send to other services/goroutines.
I ended up with the following code:
func processStatus(pid int) string {
str_pid := strconv.Itoa(pid)
cmd := exec.Command("ps", "-q", str_pid, "-o", "state", "--no-headers")
var out strings.Builder
cmd.Stdout = &out
err := cmd.Run()
if err != nil {
log.Fatal(err)
}
return out.String()
}
func main() {
// set up the command
cmd := exec.Command("./test1.sh")
var out strings.Builder
cmd.Stdout = &out
// start the command
err := cmd.Start()
if err != nil {
log.Fatal(err)
}
fmt.Println("Command has been started...")
// wait for the command/process to finish
for strings.HasPrefix(processStatus(cmd.Process.Pid), "S") {
fmt.Println("...command still running")
// report the progress
arr := strings.Split(out.String(), "\n")
if len(arr) > 1 {
fmt.Println( arr[ len(arr) - 2 ] )
} else if len(arr) == 1 {
fmt.Println( arr[0] )
}
// just to clean up the output a little
out.Reset()
// we don't know
time.Sleep(5 * time.Second)
}
err = cmd.Wait()
if err != nil {
log.Fatal(err)
}
What I found out, is the fact, that if you start the command using the cmd.Start()
, after it completes, it doesn’t create the cmd.ProcessState
struct/field. So I had to check for the process status with the ps
system command.