2012-05-28 35 views
38

Làm cách nào để tôi có thể kết hợp nhiều lệnh bên ngoài với nhau trong Go? Tôi đã thử mã này nhưng tôi nhận được một lỗi nói rằng exit status 1.Làm thế nào để ống một số lệnh trong 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) 
} 

Trả lời

30

StdoutPipe trả về một đường ống sẽ được kết nối với đầu ra tiêu chuẩn của lệnh khi lệnh bắt đầu. Đường ống sẽ tự động đóng lại sau khi Chờ thấy lệnh thoát.

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

Thực tế bạn làm c1.Wait đóng stdoutPipe.

tôi đã thực hiện một ví dụ làm việc (chỉ là một bản demo, thêm lỗi chú ý!):

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

Tại sao sử dụng io.Pipe thay vì exec.Cmd.StdoutPipe? –

+0

Tôi thích io.Pipe quá, nhưng việc bắt đầu c1 vào một goroutine riêng biệt hoạt động tốt hơn cho tôi. Xem phiên bản đã sửa đổi của tôi bên dưới. – 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

Tôi về cơ bản sử dụng cùng một mã nhưng thường tôi nhận được lỗi "đường ống bị hỏng". Bất kỳ ý tưởng gì có thể gây ra điều đó? http://stackoverflow.com/q/26122072/4063955 –

+0

@AnthonyHat: vui lòng đặt nhận xét này về câu hỏi mới của bạn, vì vậy chúng tôi có thể thấy bạn đã thấy câu hỏi này và nó không hiệu quả với bạn. – RickyA

+0

Ống bị hỏng xảy ra khi một quá trình cố gắng viết vào một đường ống nhưng mặt khác của đường ống đã bị đóng. Ví dụ, nếu "wc -l" thoát trước khi "ls" hoàn thành trong ví dụ trên, "ls" sẽ nhận được lỗi/tín hiệu Broken Pipe. – Matt

2

Đây là một ví dụ làm việc đầy đủ. Hàm Execute mất bất kỳ số lượng exec.Cmd trường hợp nào (sử dụng variadic function) và sau đó lặp lại chúng một cách chính xác khi gắn kết đầu ra của stdout vào stdin của lệnh tiếp theo. Điều này phải được thực hiện trước khi bất kỳ hàm nào được gọi.

Chức năng cuộc gọi sau đó đi về gọi các lệnh trong một vòng lặp, sử dụng trì hoãn để gọi đệ quy và đảm bảo việc đóng cửa đúng đắn của đường ống

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

Có sẵn trong ý chính này

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

Một tốt điểm cần biết là các biến hệ vỏ như ~ không được nội suy

+0

Cập nhật - trong phòng thủ của tôi, tôi đã trả lời nó lúc 5 giờ sáng sau vài giờ làm việc trên đó :) – Tyndyll

50

Đối với các trường hợp đơn giản, bạn có thể sử dụng thi s cách tiếp cận:

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

Ví dụ, chức năng này lấy tên model CPU sử dụng lệnh đường ống:

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

Giống như câu trả lời đầu tiên nhưng với lệnh đầu tiên bắt đầu và chờ đợi trong một goroutine. Điều này giữ cho ống hạnh phúc.

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() 
} 
Các vấn đề liên quan