如何在Go中运行前台或后台的shell命令

huangapple go评论68阅读模式
英文:

How to run a foreground or background shell command in Go

问题

在Go语言中,我需要能够从用户那里运行一个shell命令,并且只阻塞在前台运行的命令(不使用&运行),同时获取命令的输出。

例如,在Bash中,我们可以这样写:

#!/bin/bash
while read -p 'enter a command: ' cmd; do
	/bin/sh -c "$cmd" >(sed 's/^/line of output: /')
done

用户可以输入一个前台命令,比如 sleep 5; echo hi,它会阻塞并且提示符不会立即重新出现,或者他们可以输入一个后台命令,比如 { sleep 5; echo hi; } &,它不会阻塞并且提示符会立即重新出现,因为命令在后台运行。

我该如何在Go中重新创建这个功能?以下是我的尝试:

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

运行 sleep 5; echo hi 会阻塞并且正常工作,但是 { sleep 5; echo hi; } & 会报错:

panic: read |0: file already closed

背景信息:我正在将语音控制移植到Go语言中,用户可以配置诸如 runtype 的操作,其中他们的命令通过shell运行,并模拟按键输入输出。用户可以使用后台命令,以便在命令执行时语音控制仍然继续进行,例如,他们可以在说“menu”时启动一个菜单,并且仍然能够在菜单打开时使用语音控制,命令示例:menu: runtype printf %s\\n this that | dmenu &

英文:

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 &.

答案1

得分: 0

将stdout设置为命名管道实现了这种行为。

英文:

Setting stdout to a named pipe achieved the behavior.

huangapple
  • 本文由 发表于 2023年4月22日 05:26:29
  • 转载请务必保留本文链接:https://go.coder-hub.com/76076799.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定