执行官.修改了Stdin的Wait()将无限期等待



我遇到了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,最简单的方法是使用StdinPipeStdoutPipeStderrPipe方法,这些方法依次使用os.Pipe()。这样可以确保流程只处理*os.File,而不处理其他Go类型。

最新更新