9

I have a go program that should invoke a ruby script.

I have a runCommand function:

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

I invoke it like this:

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

It works for most cases, except if there is a gets or any similar operation in the child process that needs to pause for an input.

I have tried setting cmd.Stdin = os.Stdin, but it does not wait for input.

What am I doing wrong?

6
  • When there is gets in Ruby, can you enter input from your console? Does Ruby wait for that? Do you hit Enter after entering your input? Commented May 13, 2015 at 6:42
  • gets is in middle of a flow, and if I run the ruby script it waits for an input. Yes, I hit enter after the input. My real usecase is to invoke pry on the ruby side, and my expectation is cmd.Run() would wait for the pry REPL to complete. Commented May 13, 2015 at 6:48
  • If I run this simple Go app from your code, it works perfectly, waits for the input and properly prints the outpout. I'd say it is something in your Ruby code. Commented May 13, 2015 at 6:52
  • 2
    BTW, not at all related to your question but, you almost never need or want to directly call an errors' Error method. And if you're going to exit, you can send output to stderr and exit via the log packge. I.e. just log.Fatal("Failed to start Ruby:", err) will do. Commented May 13, 2015 at 13:39
  • Thanks for you comments everyone, I figured out that I was doing something stupid. @icza's point led me to troubleshoot it successfully. It turns out that my setup had multiple level of redirection and I missed redirecting Stdin at the first level. Commented May 14, 2015 at 13:37

2 Answers 2

8

The following program seems do what you ask for (my runCommand is almost identical to yours. I just changed the = to := for the err line.) Are you doing something differently?

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)
    }
}
Sign up to request clarification or add additional context in comments.

1 Comment

Thanks - I finally figured out that this was valid code, and problem was elsewhere (I had multiple level of redirection, and I missed one of it).
7

You might need to use a pseudoterminal. You can do this in go with this library: 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 Comment

Did not know about this library, +1 for introducing me to a pseudoterminal in golang. The problem was elsewhere, as I mentioned in my comment above. Thanks.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.