如何在Golang中使用stdin执行命令?

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

how can I get stdin to exec cmd in golang

问题

我有这段代码:

subProcess := exec.Cmd{
    Path: execAble,
    Args: []string{
        fmt.Sprintf("-config=%s", *configPath),
        fmt.Sprintf("-serverType=%s", *serverType),
        fmt.Sprintf("-reload=%t", *reload),
        fmt.Sprintf("-listenFD=%d", fd),
    },
    Dir: here,
}
subProcess.Stdout = os.Stdout
subProcess.Stderr = os.Stderr
logger.Info("starting subProcess:%s ", subProcess.Args)

if err := subProcess.Run(); err != nil {
    logger.Fatal(err)
}

然后我使用 os.Exit(1) 停止主进程

我可以从子进程中获取输出但我也想将标准输入(stdin)传递给它

我尝试了

```go
subProcess.Stdin = os.Stdin

但它不起作用。

英文:

I have this code

subProcess := exec.Cmd{
	Path: execAble,
	Args: []string{
		fmt.Sprintf("-config=%s", *configPath),
		fmt.Sprintf("-serverType=%s", *serverType),
		fmt.Sprintf("-reload=%t", *reload),
		fmt.Sprintf("-listenFD=%d", fd),
	},
	Dir: here,
}
subProcess.Stdout = os.Stdout
subProcess.Stderr = os.Stderr
logger.Info("starting  subProcess:%s ", subProcess.Args)

if err := subProcess.Run(); err != nil {
	logger.Fatal(err)
}

and then I do os.Exit(1) to stop the main process

I can get output from the subprocess

but I also want to put stdin to

I try

subProcess.Stdin = os.Stdin

but it does not work

答案1

得分: 32

我制作了一个简单的程序(用于测试)。它读取一个数字并将给定的数字输出。

package main

import (
	"fmt"
)

func main() {
	fmt.Println("Hello, What's your favorite number?")
	var i int
	fmt.Scanf("%d\n", &i)
	fmt.Println("Ah I like ", i, " too.")
}

以下是修改后的代码:

package main

import (
	"fmt"
	"io"
	"os"
	"os/exec"
)

func main() {
	subProcess := exec.Command("go", "run", "./helper/main.go") //仅用于测试,请替换为您的子进程

	stdin, err := subProcess.StdinPipe()
	if err != nil {
		fmt.Println(err) //替换为记录器或其他任何您想要的内容
	}
	defer stdin.Close() //文档说subProcess.Wait会关闭它,但我不确定,所以我保留了这行

	subProcess.Stdout = os.Stdout
	subProcess.Stderr = os.Stderr

	fmt.Println("START") //用于调试
	if err = subProcess.Start(); err != nil { //使用start,而不是run
		fmt.Println("An error occured: ", err) //替换为记录器或其他任何您想要的内容
	}

	io.WriteString(stdin, "4\n")
	subProcess.Wait()
	fmt.Println("END") //用于调试
}

你对以下这些行感兴趣:

stdin, err := subProcess.StdinPipe()
if err != nil {
    fmt.Println(err)
}
defer stdin.Close()
//...
io.WriteString(stdin, "4\n")
//...
subProcess.Wait()

上述行的解释:

  1. 我们获取子进程的标准输入(stdin),现在我们可以向其写入数据。
  2. 我们使用我们的能力,写入一个数字。
  3. 我们等待子进程完成。

输出:

START
Hello, What's your favorite number?
Ah I like 4 too.
END

更好地理解

英文:

I made a simple program (for testing). It reads a number and writes the given number out.

package main

import (
	"fmt"
)

func main() {
	fmt.Println("Hello, What's your favorite number?")
	var i int
	fmt.Scanf("%d\n", &i)
	fmt.Println("Ah I like ", i, " too.")
}

And here is the modified code

package main

import (
	"fmt"
	"io"
	"os"
	"os/exec"
)

func main() {
	subProcess := exec.Command("go", "run", "./helper/main.go") //Just for testing, replace with your subProcess

	stdin, err := subProcess.StdinPipe()
	if err != nil {
		fmt.Println(err) //replace with logger, or anything you want
	}
	defer stdin.Close() // the doc says subProcess.Wait will close it, but I'm not sure, so I kept this line

	subProcess.Stdout = os.Stdout
	subProcess.Stderr = os.Stderr

	fmt.Println("START") //for debug
	if err = subProcess.Start(); err != nil { //Use start, not run
		fmt.Println("An error occured: ", err) //replace with logger, or anything you want
	}

	io.WriteString(stdin, "4\n")
	subProcess.Wait()
	fmt.Println("END") //for debug
}

You interested about these lines

stdin, err := subProcess.StdinPipe()
if err != nil {
    fmt.Println(err)
}
defer stdin.Close()
//...
io.WriteString(stdin, "4\n")
//...
subProcess.Wait()

Explanation of the above lines

  1. We gain the subprocess' stdin, now we can write to it
  2. We use our power and we write a number
  3. We wait for our subprocess to complete

Output

> START<br>
Hello, What's your favorite number?<br>
Ah I like 4 too.<br>
END<br>

For better understanding

答案2

得分: 8

现在Go文档中有一个更新的示例可用:https://golang.org/pkg/os/exec/#Cmd.StdinPipe

如果子进程在stdin关闭之前没有继续执行,io.WriteString()调用需要包装在一个匿名函数中:

func main() {
	cmd := exec.Command("cat")
	stdin, err := cmd.StdinPipe()
	if err != nil {
		log.Fatal(err)
	}

	go func() {
		defer stdin.Close()
		io.WriteString(stdin, "将写入stdin的值传递给cmd的标准输入")
	}()

	out, err := cmd.CombinedOutput()
	if err != nil {
		log.Fatal(err)
	}

	fmt.Printf("%s\n", out)
}
英文:

There's now an updated example available in the Go docs: https://golang.org/pkg/os/exec/#Cmd.StdinPipe

If the subprocess doesn't continue before the stdin is closed, the io.WriteString() call needs to be wrapped inside an anonymous function:

func main() {
	cmd := exec.Command(&quot;cat&quot;)
	stdin, err := cmd.StdinPipe()
	if err != nil {
		log.Fatal(err)
	}

	go func() {
		defer stdin.Close()
		io.WriteString(stdin, &quot;values written to stdin are passed to cmd&#39;s standard input&quot;)
	}()

	out, err := cmd.CombinedOutput()
	if err != nil {
		log.Fatal(err)
	}

	fmt.Printf(&quot;%s\n&quot;, out)
}

答案3

得分: 3

尽管这个问题有点老,但这是我的答案:

这个问题当然非常依赖于平台,因为标准输入输出的处理方式取决于操作系统的实现,而不是Go语言。然而,作为一个经验法则(由于某些操作系统的普及),"你所问的是不可能的"。

在大多数现代操作系统上,你可以使用管道来处理标准流(就像@mraron的回答中所示),你可以将它们分离(这就是守护进程的工作方式),但你不能重新分配或委派它们给另一个进程。

我认为这种限制更多是出于安全考虑。仍然会不时发现一些允许远程代码执行的漏洞,想象一下如果操作系统允许重新分配/委派标准输入/输出,那么在这样的漏洞存在下,后果将是灾难性的。

英文:

Though this question is a little old, but here is my answer:

This question is of course very platform specific as how standard IO is handled depends on the OS implementation and not on Go language. However, as general rule of thumb (due to some OSes being prevailing), "what you ask is not possible".

On most of modern operating systems you can pipe standard streams (as in @mraron's answer), you can detach them (this is how daemons work), but you cannot reassign or delegate them to another process.

I think this limitation is more because of security concern. There are still from time to time bugs being discovered that allow remote code execution, imagine if OS was allowing to reassign/delegate STDIN/OUT, then with such vulnerabilities the consequences would be disastrous.

答案4

得分: 2

虽然正如@AlexKey之前所说,你不能直接这样做,但你可以采取一些变通方法。如果操作系统阻止你将自己的标准流导入管道,那又有谁在乎呢?你只需要两个通道和两个goroutine。

var stdinChan chan []byte
var stdoutChan chan []byte

// 当你的代码的标准输出发生变化时,将其导入stdout通道
stdoutChan <- somehowGotDataFromStdOut

然后,你需要两个之前提到的函数:

func Pipein() {
    for {
        stdinFromProg.Write(<-stdInChan)
    }
}

对于stdout,思路是一样的。

英文:

While you cannot directly do this as @AlexKey wrote earlier still you can make some workarounds. If os prevents you to pipe your own standard streams who cares all you need 2 channels and 2 goroutines

var stdinChan chan []byte
var stdoutChan chan []byte

//when something happens in stdout of your code just pipe it to stdout chan
stdoutChan&lt;-somehowGotDataFromStdOut

then you need two funcs as i mentioned before

func Pipein(){
    for {
        stdinFromProg.Write(&lt;-stdInChan)
    }
}

The same idea for the stdout

huangapple
  • 本文由 发表于 2014年4月19日 13:54:32
  • 转载请务必保留本文链接:https://go.coder-hub.com/23166468.html
匿名

发表评论

匿名网友

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

确定