【问题标题】:Pipe to exec'ed process管道到执行的进程
【发布时间】:2017-05-02 11:45:18
【问题描述】:

我的 Go 应用程序输出了一些文本数据,我需要将它传送到一些外部命令(例如 less)。我还没有找到任何方法将这些数据传送到syscall.Exec'ed 进程。

作为一种解决方法,我将该文本数据写入一个临时文件,然后将该文件用作less 的参数:

package main

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

func main() {
    content := []byte("temporary file's content")
    tmpfile, err := ioutil.TempFile("", "example")
    if err != nil {
        log.Fatal(err)
    }

    defer os.Remove(tmpfile.Name()) // Never going to happen!

    if _, err := tmpfile.Write(content); err != nil {
        log.Fatal(err)
    }
    if err := tmpfile.Close(); err != nil {
        log.Fatal(err)
    }

    binary, err := exec.LookPath("less")
    if err != nil {
        log.Fatal(err)
    }

    args := []string{"less", tmpfile.Name()}

    if err := syscall.Exec(binary, args, os.Environ()); err != nil {
        log.Fatal(err)
    }
}

它可以工作,但会在文件系统上留下一个临时文件,因为 syscall.Exec 用另一个 (less) 替换当前的 Go 进程,而延迟的 os.Remove 不会运行。这种行为是不可取的。

有什么方法可以在不留下任何人工制品的情况下将一些数据传送到外部进程?

【问题讨论】:

    标签: go pipe system-calls


    【解决方案1】:

    您应该使用os/exec 构建一个exec.Cmd 来执行,然后您可以提供任何您想要的io.Reader 作为命令的标准输入。

    来自文档中的示例:

    cmd := exec.Command("tr", "a-z", "A-Z")
    cmd.Stdin = strings.NewReader("some input")
    var out bytes.Buffer
    cmd.Stdout = &out
    err := cmd.Run()
    if err != nil {
        log.Fatal(err)
    }
    fmt.Printf("in all caps: %q\n", out.String())
    

    如果您想直接写入命令的标准输入,那么您可以调用cmd.StdInPipe 来获得一个您可以写入的io.WriteCloser

    如果你真的需要exec 进程来代替你当前的进程,你可以在执行之前简单地删除文件,并提供该文件描述符作为程序的标准输入。

    content := []byte("temporary file's content")
    tmpfile, err := ioutil.TempFile("", "example")
    if err != nil {
        log.Fatal(err)
    }
    os.Remove(tmpfile.Name())
    
    if _, err := tmpfile.Write(content); err != nil {
        log.Fatal(err)
    }
    
    tmpfile.Seek(0, 0)
    
    err = syscall.Dup2(int(tmpfile.Fd()), syscall.Stdin)
    if err != nil {
        log.Fatal(err)
    }
    
    binary, err := exec.LookPath("less")
    if err != nil {
        log.Fatal(err)
    }
    
    args := []string{"less"}
    
    if err := syscall.Exec(binary, args, os.Environ()); err != nil {
        log.Fatal(err)
    }
    

    【讨论】:

    • 谢谢,我知道os/exec et al,但它根本解决不了我的问题。我想将数据传输到类似寻呼机的环境 (less),而不是使用一些文本实用程序处理它并处理输出。
    • @PetrShevtsov:我误解了,因为您不能直接通过管道传输到要执行的进程。管道阻塞,数据必须在某处缓冲。每个人都说这不是围棋问题,因为一般情况下你不能这样做。如果您使用的是 posix 系统(我假设使用 syscall.Exec 和 less 调用),您可以删除 tmp 文件。
    • 谢谢,它有效!但是我可以摆脱临时文件并直接写入syscall.Stdin吗?
    • @PetrShevtsov:不,因为我之前说过的原因,数据需要在某个地方缓冲,并且在 Exec 调用之后写入标准输入的原始进程不再存在。有些程序也可以通过 fork 来做到这一点,并让孩子继续写入 FD 0,但你不能在 Go 中 fork。
    猜你喜欢
    • 2013-11-23
    • 1970-01-01
    • 1970-01-01
    • 2015-06-04
    • 2015-01-06
    • 2012-06-01
    • 2021-09-28
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多