使用 goroutine 执行 Bash 命令 n 次,并将结果存储和打印出来

4

我对Golang比较新,在尝试执行一个带有参数的bash命令n次,然后将输出存储在变量中并进行打印

我可以只执行一次,或者像下面这样使用循环

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

这个很好用,我能轻松地阅读输出。

现在,我想使用 goroutines 重复执行 n 次这个操作。

我尝试了遵循回答如何定义一个一次性在 Golang 中执行的 goroutine 池?的人的方法,但我无法让它工作。

到目前为止,这是我尝试过的:

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

}

但我不知道如何存储命令的i-result并将其打印出来。

我该如何做到这一点?

提前感谢您对问题的任何解释,如果需要进一步澄清,请留下评论。

1个回答

9

因此,以下方法可以解决您的问题:

  • 您可以调用Cmd.Output()方法获取命令的输出。或者,您可以将Cmd.StdOutPipe连接到一个byte.Buffer中,然后从其中读取。

  • 您的goroutine逻辑是错误的。它只会执行4次,因为waitgroup被Done()标记并且主程序退出了。在主程序关闭通道来通知工作线程退出for range循环后,您应该调用wg.Done。我使用defer关键字来实现。

  • 当您使用go命令执行匿名函数时,请尽量不要从父作用域捕获任何变量。这可能导致灾难性后果。相反,确定您需要哪些参数,并将它们传递给该函数。

`

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

}

`


完全就是我所需要的,谢谢。还有一个小问题。你认为这是使用Go进行“并行”操作最具成本效益的方法吗?我需要对大约1,000,000个不同的a [0]参数执行此操作,并将结果存储在filedb(例如MongoDB)中。 - AndreaM16
这真的取决于情况。运行goroutine是在数万个工作人员之间分配任务的绝佳方式。但恐怕在您的问题中,您可能会遇到数据库驱动程序的瓶颈。许多Golang数据库连接器可以很好地扩展到许多工作人员(我不知道MongoDB,但在我使用过的Cassandra中,请确保它们共享数据库会话)。另一方面是从脚本中运行1百万个进程。同样...命令的中位运行时间是多少?您能否通过将几千个参数合并为一个命令调用来批量操作? - ramrunner
执行这个相当慢的Ocaml加密脚本并打印它100,000次需要大约42秒。每次我都必须传递不同的6个十六进制字符并存储结果。在NodeJS中,我真的很难达到这样的性能。现在,正如你所说,我必须测试将结果保存在MongoDB上是否是一个好主意。不幸的是,我不能一次传递超过a[0]。它接受“-s”,“十六进制密钥”和“24个十六进制字符明文”。因此,我必须对000000-FFFFFF范围内的每个不同密钥执行它。 - AndreaM16
2
很抱歉,如果没有更多的细节,我无法提供帮助。但请记住,磁盘I/O速度较慢,同时向标准输出打印也可能会影响性能。建议尝试使用byte.Buffer进行写入(使用StdoutPipe或者追加Output()),然后通过计数器将整个结果缓冲区刷新到磁盘或数据库中。您不需要在每次脚本执行时都进行一次存储操作。 - ramrunner
我会深入研究,非常感谢,我真的很感激你的帮助。 - AndreaM16
显示剩余4条评论

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接