I'm trying to write a function that break when a condition is met the output of command is equal something (hello in this example) in this case or timeout is reached
timeout := 10 sec
func run() error {
for {
out , _ := exec.Command("echo", "hello").Output()
if string(out) == "hello" || timeout {
break
}
}
}
I've seen that folks use select but I don't know how to use it here, any hints?
You can use a similar code than Go by Example: Timeouts
c2 := make(chan string, 1)
go func() {
time.Sleep(2 * time.Second)
c2 <- "result 2"
}()
select {
case res := <-c2:
fmt.Println(res)
case <-time.After(3 * time.Second):
fmt.Println("timeout 2")
}
In your case, the go func
would exec
the echo
, while the main function would wait for a timeout, or the execution of go func
, whichever comes first.
That is: https://goplay.space/#MtHh3CenMcn
package main
import (
"fmt"
"os/exec"
"time"
)
func main() {
c2 := make(chan string, 1)
go func() {
out, err := exec.Command("echo", "hello").Output()
fmt.Println(err)
c2 <- string(out)
}()
select {
case res := <-c2:
fmt.Println("'" + res + "'")
case <-time.After(3 * time.Second):
fmt.Println("timeout 2")
}
}
If the echo
takes more than 3 seconds, the timeout would kick in.
Note that the example does not work i a playground setting, because:
"echo": executable file not found in $PATH
But in your local environment, it should work.
A different approach, based on context: https://goplay.space/#F2GtMLgVAAI
package main
import (
"context"
"fmt"
"os/exec"
)
func main() {
gen := func(ctx context.Context) <-chan string {
dst := make(chan string)
go func() {
out, err := exec.Command("echo", "hello").Output()
fmt.Println(err)
for {
select {
case <-ctx.Done():
return // returning not to leak the goroutine
case dst <- string(out):
return
}
}
}()
return dst
}
ctx, cancel := context.WithTimeout(context.Background(), 3)
defer cancel() // cancel when we are finished consuming integers
gen(ctx)
}
If the command runs quickly, then this simple approach might do:
deadline := time.Now().Add(10 * time.Second)
for {
out, _ := exec.Command("echo", "hello").Output()
if string(out) == "hello" || time.Now().After(deadline) {
break
}
}
An improvement is to use a context with a timeout:
ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
defer cancel()
for {
out, _ := exec.CommandContext(ctx, "echo", "hello").Output()
if string(out) == "hello" || ctx.Err() != nil {
break
}
}
The context version will kill the command on timeout. The code loops until the string is found or the context is done.