我遇到了exec的一个奇怪行为。具有修改的Stdin的Wait((。我只是修改Stdin,以便能够复制其内容,计算数据量……但这不是问题所在。
我制作这个精简程序只是为了展示奇怪的行为:
- 使用修改的Stdin,
cmd.Wait()
无限期等待…直到我按"enter"或"^C"> - 使用未修改的Stdin(取消对行
cmd.Stdin = os.Stdin
的注释(,程序可以完美地处理到最后 - 当我用Delve(
dlv debug
(启动这个程序(使用修改后的Stdin(时,程序会完美地处理到最后 - 我还在
cmd.Start()
和cmd.Wait()
之间添加了一个30秒的time.Sleep
,然后将程序附加到Delve(dlv attach PID
(。当我输入continue
时,cmd.Wait()
会无限期等待…直到我按"enter"或"^C">
我用go1.11和go1.12 测试了这些行为
package main
import (
"fmt"
"os"
"os/exec"
)
type Splitter struct {
f *os.File
fd int
}
func NewSplitter(f *os.File) *Splitter {
return &Splitter{f, int(f.Fd())}
}
func (s *Splitter) Close() error {
return s.f.Close()
}
func (s *Splitter) Read(p []byte) (int, error) {
return s.f.Read(p)
}
func (s *Splitter) Write(p []byte) (int, error) {
return s.f.Write(p)
}
func main() {
var cmd *exec.Cmd
cmd = exec.Command("cat", "foobarfile")
cmd.Stdin = NewSplitter(os.Stdin)
//cmd.Stdin = os.Stdin
cmd.Stdout = NewSplitter(os.Stdout)
cmd.Stderr = NewSplitter(os.Stderr)
cmd.Start()
cmd.Wait()
fmt.Println("done")
}
我做错什么了吗?
谢谢你的帮助。
这个程序按照您的要求复制内容。不过你也可以试试评论部分。这些评论是不言自明的,我希望它能解释你的疑问。
package main
import (
"io"
"log"
"os"
"os/exec"
)
func main() {
// Execute cat command w/ arguments
// cmd := exec.Command("cat", "hello.txt")
// Execute cat command w/o arguments
cmd := exec.Command("cat")
// Attach STDOUT stream
stdout, err := cmd.StdoutPipe()
if err != nil {
log.Println(err)
}
// Attach STDIN stream
stdin, err := cmd.StdinPipe()
if err != nil {
log.Println(err)
}
// Attach STDERR stream
stderr, err := cmd.StderrPipe()
if err != nil {
log.Println(err)
}
// Spawn go-routine to copy os's stdin to command's stdin
go io.Copy(stdin, os.Stdin)
// Spawn go-routine to copy command's stdout to os's stdout
go io.Copy(os.Stdout, stdout)
// Spawn go-routine to copy command's stderr to os's stderr
go io.Copy(os.Stderr, stderr)
// Run() under the hood calls Start() and Wait()
cmd.Run()
// Note: The PIPES above will be closed automatically after Wait sees the command exit.
// A caller need only call Close to force the pipe to close sooner.
log.Println("Command complete")
}
您正在用其他Go类型替换流程文件描述符,这些描述符通常是*os.File
。为了让stdin像流一样工作,os/exec
包需要启动goroutine来在io.Reader
和进程之间复制数据。这记录在os/exec
包中:
// Otherwise, during the execution of the command a separate
// goroutine reads from Stdin and delivers that data to the command
// over a pipe. In this case, Wait does not complete until the goroutine
// stops copying, either because it has reached the end of Stdin
// (EOF or a read error) or because writing to the pipe returned an error.
如果您查看程序中的堆栈跟踪,您会发现它正在等待io goroutines在Wait()
:中完成
goroutine 1 [chan receive]:
os/exec.(*Cmd).Wait(0xc000076000, 0x0, 0x0)
/usr/local/go/src/os/exec/exec.go:510 +0x125
main.main()
因为您现在可以控制数据流,所以您可以根据需要关闭它。如果这里不需要Stdin,那么根本不要分配它。如果要使用它,则必须Close()
它才能返回Wait()
。
另一个选项是确保正在使用*os.File
,最简单的方法是使用StdinPipe
、StdoutPipe
和StderrPipe
方法,这些方法依次使用os.Pipe()
。这样可以确保流程只处理*os.File
,而不处理其他Go类型。