给出以下示例:
// test.go
package main
import (
"fmt"
"os/exec"
)
func main() {
cmd := exec.Command("login")
in, _ := cmd.StdinPipe()
in.Write([]byte("user"))
out, err := cmd.CombinedOutput()
if err != nil {
fmt.Println("error:", err)
}
fmt.Printf("%s", out)
}
如何检测到该过程无法完成,因为它正在等待用户输入?
我试图能够运行任何脚本,但如果由于某种原因它尝试从stdin读取,则中止它。
谢谢!
答案 0 :(得分:4)
检测到该过程不会完成是一个难题。事实上,它是计算机科学中经典的“无法解决的”问题之一:Halting Problem。
通常,当您调用exec.Command并且不会传递任何输入时,它将导致程序从您的操作系统的null设备读取(请参阅exec.Cmd字段中的文档)。在您的代码中(以及我的下面),您显式创建了一个管道(尽管您应该检查StdinPipe
的错误返回,以防它未正确创建),因此您应该随后调用in.Close()
。在任何一种情况下,子进程都会获得一个EOF,而应自行清理并退出。
为了帮助那些不能正确处理输入或者让自己陷入困境的进程,一般的解决方案是使用超时。在Go中,您可以使用goroutine:
// Set your timeout
const CommandTimeout = 5 * time.Second
func main() {
cmd := exec.Command("login")
// Set up the input
in, err := cmd.StdinPipe()
if err != nil {
log.Fatalf("failed to create pipe for STDIN: %s", err)
}
// Write the input and close
go func() {
defer in.Close()
fmt.Fprintln(in, "user")
}()
// Capture the output
var b bytes.Buffer
cmd.Stdout, cmd.Stderr = &b, &b
// Start the process
if err := cmd.Start(); err != nil {
log.Fatalf("failed to start command: %s", err)
}
// Kill the process if it doesn't exit in time
defer time.AfterFunc(CommandTimeout, func() {
log.Printf("command timed out")
cmd.Process.Kill()
}).Stop()
// Wait for the process to finish
if err := cmd.Wait(); err != nil {
log.Fatalf("command failed: %s", err)
}
// Print out the output
fmt.Printf("Output:\n%s", b.String())
}
在上面的代码中,实际上有三个主要的goroutine:主要的goroutine产生子进程并等待它退出;如果没有及时停止,则在后台发送定时器goroutine以终止进程;和一个goroutine,当它准备好读取它时将输出写入程序。
答案 1 :(得分:2)
虽然这不允许你“检测”试图从stdin读取的程序,但我只是关闭stdin。这样,子进程在尝试读取时只会收到EOF。大多数程序都知道如何处理封闭的标准输入。
// All error handling excluded
cmd := exec.Command("login")
in, _ := cmd.StdinPipe()
cmd.Start()
in.Close()
cmd.Wait()
不幸的是,这意味着你不能使用组合输出,下面的代码应该允许你做同样的事情。它要求您导入bytes
包。
var buf = new(bytes.Buffer)
cmd.Stdout = buf
cmd.Stderr = buf
在cmd.Wait()
之后,您可以执行以下操作:
out := buf.Bytes()
答案 2 :(得分:1)
答案 3 :(得分:0)
最后,我将实现Kyle Lemons答案的组合并强制新进程拥有它自己的会话而没有附加终端,这样执行的命令就会知道没有终端可以读取。
// test.go
package main
import (
"log"
"os/exec"
"syscall"
)
func main() {
cmd := exec.Command("./test.sh")
cmd.SysProcAttr = &syscall.SysProcAttr{Setsid: true}
out, err := cmd.CombinedOutput()
if err != nil {
log.Fatal("error:", err)
}
log.Printf("%s", out)
}