等到条件满足或超时

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.