4

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.

Community
  • 1
  • 1
AndreaM16
  • 3,917
  • 4
  • 35
  • 74

1 Answers1

8

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")

}

`

ramrunner
  • 1,362
  • 10
  • 20
  • Totally what I was looking for, thanks. Another little question. Do you think this is the most cost-efficient way of doing "parallel" operations using `Go`? I have to do that for like `1.000.000` different `a[0]` arguments and store the result in a `file` or in a `db` (e.g. MongoDB). – AndreaM16 Oct 25 '16 at 19:44
  • This really depends. running goroutines is a fantastic way to distribute jobs on tens of thousands of workers. i'm afraid that in your problem you'll hit the wall probably with your db driver though. a lot of golang database connectors scale well with a a lot of workers (i don't know about MongoDB but in cassandra that i have used make sure to have them share the database session.). the other end is running 1mil processes from your script. again... what is the median runtime of the command? can you batch ops by consolidating a couple of thousands of arguments in one command invocation? – ramrunner Oct 25 '16 at 19:49
  • It takes like `42 seconds` to execute it and print it `100.000` times. It is a pretty slow `Ocaml encryption script`. I have to pass it different `6 Hex Chars` each time and store the result. Well, with `NodeJS` I was really struggling to get such performance. Now, as you said, I have to test if saving the results on `MongoDB` is a good idea or not. Unluckily, no, I cannot pass it more than `a[0]` per time. It accepts `-s`, `Hex Key` and `A 24 Hex Chars PlainText`. So I have to execute it per each different key in the range of `000000` - `FFFFFF`. – AndreaM16 Oct 25 '16 at 19:58
  • 2
    hmm i can't really help without more details. but keep in mind that disk i/o is slow and also printing to stdout can also be. i would investigate writing to byte.Buffers (using the StdoutPipe or even appending the Output()) and then with a counter flush that whole buffer of results to disk or a db. you don't need to do one store operation per script execution. – ramrunner Oct 25 '16 at 20:11
  • I will dig into that, thank you very much, I really appreciated your help. – AndreaM16 Oct 25 '16 at 20:19
  • no worries :) have fun with golang. it's a beautiful language. – ramrunner Oct 25 '16 at 20:20
  • Let us [continue this discussion in chat](http://chat.stackoverflow.com/rooms/126662/discussion-between-ramrunner-and-andream16). – ramrunner Oct 25 '16 at 20:25
  • For today I'm off, If you are available we can speak tomorrow around the same hour :) – AndreaM16 Oct 25 '16 at 20:59
  • Hi, if you are online and want to speak about the argument you find me on the chat you just created. – AndreaM16 Oct 27 '16 at 18:27