为了账号安全,请及时绑定邮箱和手机立即绑定

带有修改后的 Stdin 的 exec.Wait() 无限期等待

带有修改后的 Stdin 的 exec.Wait() 无限期等待

Go
心有法竹 2023-08-14 17:37:52
我在使用修改后的 Stdin 时遇到了 exec.Wait() 的奇怪行为。我只是修改 Stdin,以便能够复制其内容、计算数据量……但这不是这里的问题。我制作这个精简程序只是为了演示奇怪的行为:使用修改后的标准输入,cmd.Wait()无限期地等待......直到我按“enter”或“^C”使用未经修改的标准输入(取消注释该行cmd.Stdin = os.Stdin),程序可以完美地处理到最后。当我用 Delve () 启动这个程序(使用修改后的标准输入)时dlv debug,程序完美地处理到最后!我还在和之间添加了time.Sleep30 秒,然后将程序附加到 Delve ( )。当我输入 时,无限期地等待......直到我按“enter”或“^C”cmd.Start()cmd.Wait()dlv attach PIDcontinuecmd.Wait()我用 go1.11 和 go1.12 测试了这些行为package mainimport (    "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")}我做错了什么吗?感谢您的帮助。
查看完整描述

2 回答

?
白猪掌柜的

TA贡献1893条经验 获得超10个赞

该程序会按照您的要求复制内容。您也可以尝试评论部分。这些评论是不言自明的,我希望它能解释您的疑问。


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

}



查看完整回答
反对 回复 2023-08-14
?
精慕HU

TA贡献1845条经验 获得超8个赞

您正在用其他 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 goroutine 在以下位置完成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,StdoutPipeStderrPipe方法,后者又使用os.Pipe(). 这种方式确保进程只处理*os.File,而不处理其他 Go 类型。


查看完整回答
反对 回复 2023-08-14
  • 2 回答
  • 0 关注
  • 85 浏览
慕课专栏
更多

添加回答

举报

0/150
提交
取消
意见反馈 帮助中心 APP下载
官方微信