【问题标题】:How to open a process and record stdin and stdout properly in Go?如何在 Go 中正确打开进程并记录标准输入和标准输出?
【发布时间】:2020-03-24 08:34:41
【问题描述】:

我一直在尝试编写一个程序来记录传递给子进程的内容并实时返回控制台(将来,记录 SSH 会话,现在在 Python shell 上进行测试)

我可以毫无问题地记录 stdout 和 stderr (它显示并正确记录)但我找不到在 stdin 上做同样事情的方法? 基本上,我的标准输入将映射到子进程标准输入并写入日志文件。

这是我当前的代码:

func SSH(cmd *cobra.Command, args []string) {
    logFile := fmt.Sprintf("%v@%s.log", args[0], time.Now().Format(SSHLogDateFormat))
    usr, _ := user.Current()
    home := usr.HomeDir
    logDir := fmt.Sprintf("%s/%s/logs", home, config.ConfigDir)

    if _, err := os.Stat(logDir); os.IsNotExist(err) {
        err = os.Mkdir(logDir, os.FileMode(int(0700)))
        if err != nil {
            log.Fatalf("Failed to create %s: %s", logDir, err)
        }
    }

    fullLogFile := fmt.Sprintf("%s/%s", logDir, logFile)

    log.Infof("Started recording to %s", fullLogFile)

    bash, err := exec.LookPath("bash")
    if err != nil {
        log.Errorf("Could not locate bash: %v", err)
    }

    f, err := os.Create(fullLogFile)
    if err != nil {
        log.Fatalf("Failed to open device logs: %s", err)
    }

    command := exec.Command(bash, "-c", "python")

    out := io.MultiWriter(os.Stdout, f)

    command.Stderr = out
    command.Stdout = out


    if err := command.Start(); nil != err {
        log.Fatalf("Error starting program: %s, %s", command.Path, err.Error())
    }
    err = command.Wait()
    if err != nil {
        log.Fatalf("Error waiting program: %s, %s", command.Path, err.Error())
    }


    f.Close()
    log.Infof("Finished recording to %s", fullLogFile)
}


也试过了,没有成功:

    out := io.MultiWriter(os.Stdout, f)
    in := io.TeeReader(os.Stdin, out)

    command.Stderr = out
    command.Stdout = out
    command.Stdin = in

【问题讨论】:

  • golang.org/pkg/io/#TeeReader。与 MultiWriters 的原理相同,但作为一个 io.Reader 可以分配给 Stdin。
  • 这不是很清楚我们如何呢?我尝试了以下事情:(添加到帖子中)但看起来它是错误的方式?
  • "TeeReader 返回一个读取器,它将从 r 读取的内容写入 w。"我不确定我可以添加什么使它更清楚。我假设你想要的是command.Stdin = io.TeeReader(os.Stdin, f)。但由于您没有告诉我们您所说的“没有成功”是什么意思,我们只能猜测。
  • 发生的情况是它看起来像覆盖了其他编写器,因为我只看到什么类型(并且只有那个被记录),stdout 被忽略了。

标签: go io subprocess pipe pipeline


【解决方案1】:

您需要写信到进程的stdin。获取一个写入管道:

procIn, err := command.StdinPipe()
if nil!=err {
  log.Fatal(err)
}

然后创建一个 multiWriter 来写入日志和进程:

inWriter := io.MultiWriter(procIn, f)

最后,将 Stdin 复制到 MultiWriter 中:

go func() {
  io.Copy(inWriter, os.Stdin)
  procIn.Close()
}()

我们在 goroutine 中进行复制,以免把所有东西都挂掉:我们还没有启动命令,所以没有任何东西接收到写入的字节。它需要与运行的命令并行发生。

这是一个非常简单的例子:

package main

import (
    `os`
    `os/exec`
    `io`
)

// pipeto copies stdin to logOut and to the command,
// and copies the commands stdout and stderr to logOut and
// to our stderr.
func pipeto(logOut os.Writer, cmd string, args ...string) error {
    cmd := exec.Command(cmd, args...)

    out := io.MultiWriter(os.Stdout, logOut)
    cmd.Stderr, cmd.Stdout = out, out

    procIn, err := cmd.StdinPipe()
    if nil!=err {
        return err
    }

    go func() {
        io.Copy( io.MultiWriter(procIn, logOut) , os.Stdin )
        procIn.Close()
    }()

    return cmd.Run()
}

func main() {
    logOut, err := os.Create(`logout.log`)
    if nil!=err {
        panic(err)
    }
    defer logOut.Close()
    if err := pipeto(logOut, `sed`, `s/tea/coffee/g`); nil!=err {
        panic(err)
    }
}

您可以对其进行测试,我将我的 go 文件命名为 pipetest.go

echo this is a test of tea | go run pipetest.go

您将看到logout.log 中反映的输入和输出:

this is a test of tea
this is a test of coffee

【讨论】:

  • 到目前为止运气不好,尝试这个时我没有得到任何输出,看起来它与其他作家搞砸了?
  • 我建议删除你的作者,只使用我在示例中演示的那些。如果我正确理解了您的问题,那么该示例可以满足您的要求...
【解决方案2】:

最后我通过使用 PTY 库找到了解决方案(无论如何处理子进程上的特殊信号和选项卡都需要它):https://github.com/creack/pty

我以Shell 为例,只是将io.Copy 替换为我的MultiWriter

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2016-03-04
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2010-12-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多