Вопрос по – Как передать несколько команд в Go?

54

Как я могу передать несколько внешних команд вместе в Go? Я пробовал этот код, но получаю сообщение об ошибкеexit status 1.

package main

import (
    "io"
    "log"
    "os"
    "os/exec"
)

func main() {
    c1 := exec.Command("ls")
    stdout1, err := c1.StdoutPipe()
    if err != nil {
        log.Fatal(err)
    }

    if err = c1.Start(); err != nil {
        log.Fatal(err)
    }
    if err = c1.Wait(); err != nil {
        log.Fatal(err)
    }

    c2 := exec.Command("wc", "-l")
    c2.Stdin = stdout1

    stdout2, err := c2.StdoutPipe()
    if err != nil {
        log.Fatal(err)
    }

    if err = c2.Start(); err != nil {
        log.Fatal(err)
    }
    if err = c2.Wait(); err != nil {
        log.Fatal(err)
    }

    io.Copy(os.Stdout, stdout2)
}

Ваш Ответ

6   ответов
43
package main

import (
    "os"
    "os/exec"
)

func main() {
    c1 := exec.Command("ls")
    c2 := exec.Command("wc", "-l")
    c2.Stdin, _ = c1.StdoutPipe()
    c2.Stdout = os.Stdout
    _ = c2.Start()
    _ = c1.Run()
    _ = c2.Wait()
}
Сломанный канал возникает, когда один процесс пытается записать в канал, но другая сторона канала уже закрыта. Например, если & quot; wc -l & quot; выход до "ls" закончил в вышеприведенном примере, "ls" получит ошибку / сигнал Broken Pipe.
Как я могу сделать эту программу параллельной, так как здесь происходит io (stdin / stdout) !!
@AnthonyHat: пожалуйста, оставьте этот комментарий на своем новом вопросе, чтобы мы могли видеть, что вы видели этот вопрос, и он не работает для вас.
Я в основном использую один и тот же код, но часто получаю «сломанную трубу» ошибка. Есть идеи, что может вызвать это?stackoverflow.com/q/26122072/4063955
@ user7044, я не уверен, что ты имеешь в виду. Две команды & lt; ls & quot; и & quot; wc -l & quot; в этом примере выполняются одновременно с выводом из ls по конвейеру в wc, который может начать чтение вывода из ls до того, как ls завершит запись всего этого.
38

StdoutPipe returns a pipe that will be connected to the command's standard output when the command starts. The pipe will be closed automatically after Wait sees the command exit.

(отhttp://golang.org/pkg/os/exec/#Cmd.StdinPipe )

Тот факт, что вы делаетеc1.Wait закрываетstdoutPipe.

Я сделал рабочий пример (просто демо, добавь ловлю ошибок!):

package main

import (
    "bytes"
    "io"
    "os"
    "os/exec"
)

func main() {
    c1 := exec.Command("ls")
    c2 := exec.Command("wc", "-l")

    r, w := io.Pipe() 
    c1.Stdout = w
    c2.Stdin = r

    var b2 bytes.Buffer
    c2.Stdout = &b2

    c1.Start()
    c2.Start()
    c1.Wait()
    w.Close()
    c2.Wait()
    io.Copy(os.Stdout, &b2)
}
Почему использовать io.Pipe, а не exec.Cmd.StdoutPipe?
Мне тоже нравится io.Pipe, но лучше запустить c1 start в отдельную программу. Смотрите мою модифицированную версию ниже.
77

Для простых сценариев вы можете использовать этот подход:

bash -c "echo 'your command goes here'"

Например, эта функция извлекает имя модели процессора с помощью команд по конвейеру:

func getCPUmodel() string {
        cmd := "cat /proc/cpuinfo | egrep '^model name' | uniq | awk '{print substr($0, index($0,$4))}'"
        out, err := exec.Command("bash","-c",cmd).Output()
        if err != nil {
                return fmt.Sprintf("Failed to execute command: %s", cmd)
        }
        return string(out)
}
Однако следует отметить, что это не удастся, если cmd слишком длинный. В частности, если его длина превышает 131072 байта, вы, вероятно, получите что-то вродеfork/exec /bin/bash: argument list too long, увидетьhere, В этом случае вы можете изменить или разделить свою команду, или прибегнуть к более здоровеннымio.Pipe методы, перечисленные в других местах в ответах на этот вопрос.
4

Вроде первый ответ, но с первой командой запустили и ждали в программе. Это держит трубку счастливой.

package main

import (
    "io"
    "os"
    "os/exec"
)

func main() {
    c1 := exec.Command("ls")
    c2 := exec.Command("wc", "-l")

    pr, pw := io.Pipe()
    c1.Stdout = pw
    c2.Stdin = pr
    c2.Stdout = os.Stdout

    c1.Start()
    c2.Start()

    go func() {
        defer pw.Close()

        c1.Wait()
    }()
    c2.Wait()
}
@JasonStewart Этот совет кажется правильным, спасибо. Я уже начал использовать его без каких-либо побочных эффектов.
Скорее всего, он будет работать без рутины, если он будет использовать os.Pipe () вместо io.Pipe (). Пусть ОС сама выполняет байт-тасование.
2

Это полностью рабочий пример.Execute функция занимает любое количествоexec.Cmd экземпляры (используяпеременная функция), а затем перебирает их правильно, подключая вывод stdout к stdin следующей команды. Это должно быть сделано до вызова любой функции.

Затем функция вызова выполняет вызов команд в цикле, используя defers для рекурсивного вызова и обеспечения надлежащего закрытия каналов.

package main

import (
    "bytes"
    "io"
    "log"
    "os"
    "os/exec"
)

func Execute(output_buffer *bytes.Buffer, stack ...*exec.Cmd) (err error) {
    var error_buffer bytes.Buffer
    pipe_stack := make([]*io.PipeWriter, len(stack)-1)
    i := 0
    for ; i < len(stack)-1; i++ {
        stdin_pipe, stdout_pipe := io.Pipe()
        stack[i].Stdout = stdout_pipe
        stack[i].Stderr = &error_buffer
        stack[i+1].Stdin = stdin_pipe
        pipe_stack[i] = stdout_pipe
    }
    stack[i].Stdout = output_buffer
    stack[i].Stderr = &error_buffer

    if err := call(stack, pipe_stack); err != nil {
        log.Fatalln(string(error_buffer.Bytes()), err)
    }
    return err
}

func call(stack []*exec.Cmd, pipes []*io.PipeWriter) (err error) {
    if stack[0].Process == nil {
        if err = stack[0].Start(); err != nil {
            return err
        }
    }
    if len(stack) > 1 {
        if err = stack[1].Start(); err != nil {
             return err
        }
        defer func() {
            if err == nil {
                pipes[0].Close()
                err = call(stack[1:], pipes[1:])
            }
        }()
    }
    return stack[0].Wait()
}

func main() {
    var b bytes.Buffer
    if err := Execute(&b,
        exec.Command("ls", "/Users/tyndyll/Downloads"),
        exec.Command("grep", "as"),
        exec.Command("sort", "-r"),
    ); err != nil {
        log.Fatalln(err)
    }
    io.Copy(os.Stdout, &b)
}

Доступно в этой сути

https://gist.github.com/tyndyll/89fbb2c2273f83a074dc

Полезно знать, что переменные оболочки, такие как ~, не интерполируются

Обновлено - в свою защиту я ответил на него в 5 часов утра после нескольких часов работы над ним :)
0
package main

import (
    ...
    pipe "github.com/b4b4r07/go-pipe"
)

func main() {
    var b bytes.Buffer
    pipe.Command(&b,
        exec.Command("ls", "/Users/b4b4r07/Downloads"),
        exec.Command("grep", "Vim"),
    )

    io.Copy(os.Stdout, &b)
}

Я провел хороший день, пытаясь использоватьДенис Сюрет Ответ, чтобы придумать обертку для несколькихexec.Command прежде чем я наткнулсяэтот аккуратный пакет отb4b4r07.

Я только что понял, что реализация этого пакета такая же, как и ответ @Tyndyll выше. Просто отмечая ...

Похожие вопросы