【问题标题】:golang exec.Command read std inputgolang exec.Command 读取标准输入
【发布时间】:2015-07-24 06:44:46
【问题描述】:

我有一个应该调用 ruby​​ 脚本的 go 程序。

我有一个runCommand 函数:

func runCommand(cmdName string, arg ...string) {
    cmd := exec.Command(cmdName, arg...)
    cmd.Stdout = os.Stdout
    cmd.Stderr = os.Stderr
    cmd.Stdin = os.Stdin
    err = cmd.Run()
    if err != nil {
        fmt.Printf("Failed to start Ruby. %s\n", err.Error())
        os.Exit(1)
    }
}

我这样调用它:

runCommand("ruby", "-e", "require 'foo'")

它适用于大多数情况,除非子进程中有 gets 或任何类似的操作需要暂停输入。

我尝试设置cmd.Stdin = os.Stdin,但它不等待输入。

我做错了什么?

【问题讨论】:

  • 当Ruby中有gets时,你可以从你的控制台输入吗?鲁比会等吗?输入后是否按 Enter 键?
  • gets 处于流程中间,如果我运行 ruby​​ 脚本,它会等待输入。是的,我在输入后按回车键。我真正的用例是在 ruby​​ 端调用 pry,而我的期望是 cmd.Run() 会等待 pry REPL 完成。
  • 如果我从您的代码中运行this simple Go app,它会完美运行,等待输入并正确打印输出。我会说这是你的 Ruby 代码中的东西。
  • 顺便说一句,与您的问题完全无关,但是您几乎不需要或不想直接调用错误的Error 方法。如果您要退出,您可以将输出发送到 stderr 并通过 log 包退出。 IE。只需log.Fatal("Failed to start Ruby:", err) 即可。
  • 谢谢大家,我发现我在做一些愚蠢的事情。 @icza 的观点使我成功地对其进行了故障排除。事实证明,我的设置有多个级别的重定向,我错过了在第一级重定向Stdin

标签: go


【解决方案1】:

您可能需要使用pseudoterminal。你可以在这个库中执行此操作:github.com/kr/pty:

package main

import (
    "bufio"
    "io"
    "log"
    "os"
    "os/exec"

    "github.com/kr/pty"
)

func runCommand(cmdName string, arg ...string) {
    cmd := exec.Command(cmdName, arg...)
    tty, err := pty.Start(cmd)
    if err != nil {
        log.Fatalln(err)
    }
    defer tty.Close()

    go func() {
        scanner := bufio.NewScanner(tty)
        for scanner.Scan() {
            log.Println("[" + cmdName + "] " + scanner.Text())
        }
    }()
    go func() {
        io.Copy(tty, os.Stdin)
    }()

    err = cmd.Wait()
    if err != nil {
        log.Fatalln(err)
    }
}

func main() {
    log.SetFlags(0)
    runCommand("ruby", "-e", `
puts "Enter some text"
text = gets
puts text
  `)
}

【讨论】:

  • 不知道这个库,+1 用于向我介绍 golang 中的伪终端。正如我在上面的评论中提到的,问题出在其他地方。谢谢。
【解决方案2】:

以下程序似乎可以满足您的要求(我的runCommand 与您的几乎相同。我只是将err 行的= 更改为:=。)您在做不同的事情吗?

package main

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

func main() {
    runCommand("ruby", "-e", `puts "Running"; $in = gets; puts "You said #{$in}"`)
}

func runCommand(cmdName string, arg ...string) {
    cmd := exec.Command(cmdName, arg...)
    cmd.Stdout = os.Stdout
    cmd.Stderr = os.Stderr
    cmd.Stdin = os.Stdin
    err := cmd.Run()
    if err != nil {
        fmt.Printf("Failed to start Ruby. %s\n", err.Error())
        os.Exit(1)
    }
}

【讨论】:

  • 谢谢 - 我终于发现这是有效的代码,问题出在其他地方(我有多个级别的重定向,但我错过了其中一个)。