执行外部程序/脚本并检测它是否请求用户输入

时间:2012-11-17 17:29:28

标签: go

给出以下示例:

// 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读取,则中止它。

谢谢!

4 个答案:

答案 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)

我认为解决方案是使用关闭的标准输入运行子进程 - 通过适当调整Cmd。Stdin然后Run然后调整它而不是使用CombinedOutput()。

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