Go – Execute a Bash Command n Times using goroutines and Store & Print its result

Issue

I’m pretty new to Golang in general and I’m trying to execute a bash command with its arguments n times, then, store the Output in a variable and Print it.

I’m able to do it just one time, or just using loops like the following:

package main

import (
    "fmt"
    "os/exec"
    "os"
    "sync"
)


func main() {

    //Default Output
    var (
        cmdOut []byte
        err    error
    )

    //Bash Command
    cmd  := "./myCmd"
   //Arguments to get passed to the command
   args := []string{"arg1", "arg2", "arg3"}

    //Execute the Command
    if cmdOut, err = exec.Command(cmd, args...).Output(); err != nil {
        fmt.Fprintln(os.Stderr, "There was an error running "+cmd+" "+args[0]+args[1]+args[2], err)
        os.Exit(1)
    }
    //Store it
    sha := string(cmdOut)
    //Print it
    fmt.Println(sha)
}

This works just fine, I’m able to read the output easily.

Now, I would like to repeat this very same operation for n times, using goroutines.

I tried following the very same approach of the guy who answered How would you define a pool of goroutines to be executed at once in Golang? but I’m not able to make it work.

That’s what I tried so far:

package main

import (
    "fmt"
    "os/exec"
    "sync"
)


func main() {

    //Bash Command
    cmd  := "./myCmd"
    //Arguments to get passed to the command
     args := []string{"arg1", "arg2", "arg3"}

    //Common Channel for the goroutines
    tasks := make(chan *exec.Cmd, 64)

    //Spawning 4 goroutines
    var wg sync.WaitGroup
    for i := 0; i < 4; i++ {
        wg.Add(1)
        go func() {
            for cmd := range tasks {
                cmd.Run()
            }
            wg.Done()
        }()
    }

    //Generate Tasks
    for i := 0; i < 10; i++ {
        tasks <- exec.Command(cmd, args...)
        //Here I should somehow print the result of the latter command
    }
    close(tasks)

    // wait for the workers to finish
    wg.Wait()

    fmt.Println("Done")

}

But, I don’t really find out how to store the i-result of an executed command and print it.

How can I achieve this?

Thanks in advance, for any clarification on the question just leave a comment.

Solution

so the following fixes your problem

  • you can call Cmd.Output() to get the output of the command.
    otherwise you could connect the Cmd.StdOutPipe pipe to a byte.Buffer for
    example and read from that.

  • your goroutine logic was wrong. it would only execute 4 times cause then the waitgroup would be Done() and main would exit. You are correct to close the channel from main to signal the workers to exit the for range loop. wg.Done should be called after that so i defered it.

  • when you execute anonymous functions with the go command try not to capture anything from the parent scope. it can lead to disaster. instead figure out which parameters you want and pass them to the function.

`

package main 

import (
    "fmt"
    "os/exec"
    "sync"
)
func main() {
    cmd := "./foo.sh"
    //Arguments to get passed to the command
    args := []string{"bar", "baz"}

    //Common Channel for the goroutines
    tasks := make(chan *exec.Cmd, 64)

    //Spawning 4 goroutines
    var wg sync.WaitGroup
    for i := 0; i < 4; i++ {
            wg.Add(1)
            go func(num int, w *sync.WaitGroup) {
                    defer w.Done()
                    var (
                            out []byte
                            err error
                    )
                    for cmd := range tasks { // this will exit the loop when the channel closes
                            out, err = cmd.Output()
                            if err != nil {
                                    fmt.Printf("can't get stdout:", err)
                            }
                            fmt.Printf("goroutine %d command output:%s", num, string(out))
                    }
            }(i, &wg)
    }
    //Generate Tasks
    for i := 0; i < 10; i++ {
            tasks <- exec.Command(cmd, args...)
    }
    close(tasks)

    // wait for the workers to finish
    wg.Wait()

    fmt.Println("Done")

}

`

Answered By – ramrunner

Answer Checked By – Senaida (GoLangFix Volunteer)

Leave a Reply

Your email address will not be published.