In Go, I need to be able to run a shell command from the user, and to only block for commands run in the foreground (not run with &
), while retrieving the output of the command.
For example, in Bash we can have:
#!/bin/bash
while read -p 'enter a command: ' cmd; do
/bin/sh -c "$cmd" >(sed 's/^/line of output: /')
done
The user can enter a foreground command like sleep 5; echo hi
and it will block and the prompt won't reappear instantly, or they can enter a background command like { sleep 5; echo hi; } &
and it won't block and the prompt will reappear instantly as the command runs in the background.
How would I recreate this in Go? Here is my attempt:
package main
import (
"bufio"
"os"
"os/exec"
)
func main() {
input := bufio.NewScanner(os.Stdin)
for {
print("enter a command: ")
input.Scan()
cmd := exec.Command("/bin/sh", "-c", input.Text())
stdout, err := cmd.StdoutPipe()
if err != nil {
panic(err)
}
go func() {
output := bufio.NewScanner(stdout)
for output.Scan() {
println("line of output: " + output.Text())
}
if output.Err() != nil {
panic(output.Err())
}
}()
if err := cmd.Start(); err != nil {
panic(err)
}
if err := cmd.Wait(); err != nil {
panic(err)
}
}
}
Running sleep 5; echo hi
blocks and works, but { sleep 5; echo hi; } &
errors with:
panic: read |0: file already closed
For context, I'm porting voice control to Go, where the user can configure actions such as runtype
, where their command is run through the shell and keys are simulated to type the output. The user can use a background command so the voice control continues along with the command, for example, they can have it launch a menu when they say "menu" and still be able to use the voice control while it's open with: menu: runtype printf %s\\n this that | dmenu &
.
Setting stdout to a named pipe achieved the behavior.