管道到执行的进程

huangapple go评论93阅读模式
英文:

Pipe to exec'ed process

问题

我的Go应用程序输出一些文本数据,我需要将其导入到某个外部命令(例如less)中。我没有找到任何将这些数据导入到syscall.Exec进程中的方法。

作为一种解决方法,我将文本数据写入临时文件,然后将该文件作为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()) // 永远不会执行!

	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不会执行。这种行为是不可取的。

有没有办法在不留下任何痕迹的情况下将数据导入到外部进程中?

英文:

My Go application outputs some amounts of text data and I need to pipe it to some external command (e.g. less). I haven't find any way to pipe this data to syscall.Exec'ed process.

As a workaround I write that text data to a temporary file and then use that file as an argument to 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)
	}
}

It works but leaves a temporary file on a file system, because syscall.Exec replaces the current Go process with another (less) one and deferred os.Remove won't run. Such behaviour is not desirable.

Is there any way to pipe some data to an external process without leaving any artefacts?

答案1

得分: 2

你应该使用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命令,你可以在执行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)
}
英文:

You should be using os/exec to build an exec.Cmd to execute, then you could supply any io.Reader you want as the stdin for the command.

From the example in the documentation:

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

If you want to write directly to the command's stdin, then you call cmd.StdInPipe to get an io.WriteCloser you can write to.

If you really need to exec the process in place of your current one, you can simply remove the file before exec'ing, and provide that file descriptor as stdin for the program.

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

huangapple
  • 本文由 发表于 2016年12月17日 00:18:12
  • 转载请务必保留本文链接:https://go.coder-hub.com/41188453.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定