管道到执行过程

Pipe to exec'ed process

我的 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 将当前的 Go 进程替换为另一个 (less) 并且延迟 os.Remove 不会 运行。这种行为是不可取的。

有没有办法在不留下任何人工制品的情况下将一些数据通过管道传输到外部进程?

您应该使用 os/exec to build an 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)
}