1

给出以下示例:

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

我如何检测到该过程不会完成,因为它正在等待用户输入?

我试图能够运行任何脚本,但如果由于某种原因它试图从标准输入读取,则中止它。

谢谢!

4

4 回答 4

4

检测过程不会完成是一个难题。事实上,它是计算机科学中经典的“无法解决”问题之一:停机问题

通常,当您调用 exec.Command 并且不会向其传递任何输入时,它会导致程序从您的操作系统的空设备中读取(请参阅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,当它准备好读取它时将输出写入程序。

于 2012-11-18T04:12:09.787 回答
2

虽然这不允许您“检测”试图从标准输入读取的程序,但我只会关闭标准输入。这样,子进程在尝试读取时只会收到 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()
于 2012-11-17T18:53:05.123 回答
1

我认为解决方案是使用封闭的 stdin 运行子进程 - 通过适当调整Cmd .Stdin 然后运行它而不是使用 CombinedOutput()。

于 2012-11-17T18:45:24.317 回答
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)
}
于 2012-11-18T20:01:32.747 回答