Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

golang exec.Command read std input

Tags:

go

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?

like image 442
Srikanth Venugopalan Avatar asked May 13 '15 06:05

Srikanth Venugopalan


2 Answers

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
  `)
}
like image 98
Caleb Avatar answered Oct 23 '22 17:10

Caleb


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)
    }
}
like image 8
Rob Napier Avatar answered Oct 23 '22 17:10

Rob Napier