2012-05-28 10 views
38

Come posso unire più comandi esterni in Go? Ho provato questo codice ma ottengo un errore che dice exit status 1.Come convogliare più comandi 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) 
} 

risposta

30

StdoutPipe restituisce un tubo che verrà collegato al standard output del comando quando il comando avvia. La pipe verrà chiusa automaticamente dopo che Wait vede il comando uscire.

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

Il fatto che fai c1.Wait chiude il stdoutPipe.

Ho fatto un esempio di lavoro (solo una demo, aggiungere errore di cattura!):

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

Perché utilizzare io.Pipe anziché exec.Cmd.StdoutPipe? –

+0

Mi piace anche io.Pipe, ma l'avvio di c1 in una goroutine separata funziona meglio per me. Vedi la mia versione modificata qui sotto. – 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

Sto praticamente usando lo stesso codice, ma spesso ottengo un errore "pipe rotto". Qualche idea su cosa potrebbe causarlo? http://stackoverflow.com/q/26122072/4063955 –

+0

@AnthonyHat: si prega di mettere questo commento sulla vostra nuova domanda, in modo che possiamo vedere che hai visto questo e non ha funzionato per voi. – RickyA

+0

Il pipe rotto si verifica quando un processo tenta di scrivere in una pipe ma l'altro lato della pipe è già stato chiuso. Ad esempio, se "wc -l" è terminato prima che "ls" termini nell'esempio precedente, la "ls" otterrebbe un errore/segnale Broken Pipe. – Matt

2

Questo è un esempio completamente funzionante. La funzione Execute prende qualunque numero di istanze exec.Cmd (utilizzando un variadic function) e poi passanti sopra correttamente allegando l'output di stdout per stdin del comando successivo. Questo deve essere fatto prima di chiamare qualsiasi funzione.

La funzione di chiamata passa poi di chiamare i comandi in un ciclo, utilizzando rinvia per chiamare in modo ricorsivo e per garantire la chiusura dei tubi

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

disponibile in questo gist

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

Un buon punto da sapere è che le variabili di shell come ~ non sono interpolati

+0

Aggiornato - in mia difesa avevo risposto alle 5 del mattino dopo alcune ore di lavoro su di esso :) – Tyndyll

50

per gli scenari semplici, è possibile utilizzare thi approccio s:

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

Ad esempio, questa funzione recupera il nome del modello della CPU utilizzando i comandi pipe:

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

Come la prima risposta, ma con il primo comando iniziato e attese in un goroutine. Ciò mantiene felice la pipa.

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