2012-05-28 2 views
38

Wie kann ich mehrere externe Befehle in Go verbinden? Ich habe diesen Code ausprobiert, aber ich bekomme einen Fehler, der exit status 1 sagt.Wie mehrere Befehle in Go?

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

Antwort

30

StdoutPipe gibt ein Rohr, das auf den Befehl des Standardausgabe, wenn der Befehl beginnt verbunden wird. Die Leitung wird automatisch geschlossen, nachdem Wait den Befehl exit sieht.

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

Die Tatsache, Sie c1.Wait tun schließt die stdoutPipe.

Ich habe ein funktionierendes Beispiel (nur eine Demo, fügen Fehler Fang!):

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

Warum verwende ich io.Pipe statt exec.Cmd.StdoutPipe? –

+0

Ich mag io.Pipe auch, aber den C1-Start in eine separate Goroutine zu setzen funktioniert besser für mich. Siehe meine modifizierte Version unten. – WeakPointer

40
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() 
} 
+0

Ich benutze grundsätzlich den gleichen Code, aber oft bekomme ich einen "gebrochenen Rohr" -Fehler. Irgendeine Idee, was könnte das verursachen? http://stackoverflow.com/q/26122072/4063955 –

+0

@AnthonyHat: Bitte setzen Sie diesen Kommentar auf Ihre neue Frage, damit wir sehen können, dass Sie diese gesehen haben und es nicht für Sie funktioniert hat. – RickyA

+0

Die unterbrochene Leitung tritt auf, wenn ein Prozess versucht, in eine Leitung zu schreiben, die andere Seite der Leitung jedoch bereits geschlossen wurde. Wenn zum Beispiel "wc -l" beendet wird, bevor "ls" in dem obigen Beispiel beendet ist, würde das "ls" einen Broken Pipe-Fehler/Signal erhalten. – Matt

2

Dies ist ein voll funktionsfähiges Beispiel. Die Execute-Funktion nimmt eine beliebige Anzahl von exec.Cmd Instanzen (unter Verwendung einer variadic function) und führt dann eine Schleife über sie korrekt die Ausgabe von stdout an die stdin des nächsten Befehls. Dies muss geschehen, bevor eine Funktion aufgerufen wird.

Die Anruffunktion und der Sicherstellung der Verschluß von Rohrleitungen

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

In dieser GIST

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

Eine gute rekursiv aufzurufen, die Befehle in einer Schleife zu rufen, indem aufschiebt dann geht Punkt zu wissen ist, dass Shell-Variablen wie ~ sind nicht interpoliert

+0

Aktualisiert - zu meiner Verteidigung hatte ich es um 5 Uhr morgens nach ein paar Stunden beantwortet :) – Tyndyll

50

Für einfache Szenarien könnten Sie Thi verwenden Ansatz:

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

Zum Beispiel ruft diese Funktion des CPU-Modell verrohrt Befehle:

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

Wie die ersten Antwort, aber mit dem ersten Befehl gestartet und wartete in einem goroutine. Das hält die Pfeife glücklich.

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