【问题标题】:How to call an external program and process its output?如何调用外部程序并处理其输出?
【发布时间】:2020-06-26 01:15:57
【问题描述】:

我正在尝试调用外部命令(例如,seq 10)并获取其输出,处理输出,然后打印出处理后的结果。但是下面的代码不起作用。你能告诉我如何让它工作吗?

// vim: set noexpandtab tabstop=2:

package main

import (
    "bufio"
    "io"
    "os"
    "log"
    "os/exec"
)

func main() {
    cmd := exec.Command("seq", "10")
    stdin := bufio.NewReader(cmd.Stdout)
    err := cmd.Run()
    if err != nil {
        log.Fatalf("cmd.Run() failed with %s\n", err)
    }

    for {
        line, err := stdin.ReadBytes('\n')
        if err == io.EOF {
            if len(line) == 0 { break }
        } else {
            if err != nil { log.Fatal(err) }
            line = line[:(len(line)-1)]
        }
        os.Stdout.Write(line)
        os.Stdout.Write([]byte{'\n'})
    }
}
$ $ go run main.go 
# command-line-arguments
./main.go:15:30: cannot use cmd.Stdout (type io.Writer) as type io.Reader in argument to bufio.NewReader:
    io.Writer does not implement io.Reader (missing Read method)

编辑:我也试过这个。但它也有错误。谁能给我一个可行的例子。

// vim: set noexpandtab tabstop=2:

package main

import (
    "bufio"
    "io"
    "os"
    "log"
    "os/exec"
)

func main() {
    cmd := exec.Command("seq", "10")
    stdout, err := cmd.StdoutPipe()
    if err != nil { log.Fatal(err) }

    stdin := bufio.NewReader(stdout)
    err = cmd.Run()
    if err != nil {
        log.Fatalf("cmd.Run() failed with %s\n", err)
    }

    for {
        line, err := stdin.ReadBytes('\n')
        if err == io.EOF {
            if len(line) == 0 { break }
        } else {
            if err != nil { log.Fatal(err) }
            line = line[:(len(line)-1)]
        }
        os.Stdout.Write(line)
        os.Stdout.Write([]byte{'\n'})
    }
}

【问题讨论】:

  • golang.org/pkg/os/exec/#example_Command 中的示例应该很有用。先试试看那里。
  • @PaSTE 那里的例子不清楚。您能否展示有关如何使用 ReadBytes 处理外部命令输出的完整代码?
  • @user1424739:如果要使用bufio.Reader,请使用第一条评论中的StdoutPipe,其中也有完整的示例。
  • @JimB 我无法让它工作。您能给我看一个使用 ReadBytes 的工作示例吗?

标签: go


【解决方案1】:

另一种方法(更简洁的方法)是使用bufio.Scanner 自动处理\n(或任何其他分隔符)。另一个优点是这种方法没有种族问题(去过那里,做过):

package main

import (
    "bufio"
    "fmt"
    "log"
    "os/exec"
)

func main() {
    cmd := exec.Command("seq", "10")
    stdout, err := cmd.StdoutPipe()
    if err != nil {
        log.Fatal(err)
    }
    err = cmd.Start()
    if err != nil {
        log.Fatalf("cmd.Start() failed with %s\n", err)
    }

    stdin := bufio.NewScanner(stdout)
    for stdin.Scan() {
        fmt.Println(stdin.Text())
    }
    cmd.Wait()
}

stdin.Scan()EOF 上返回 false,这是在进程退出时给出的。 cmd.Wait() 将关闭 StdoutPipe,您可以阅读 err.(exec.ExitError).ExitCode() 以获取退出代码(如果退出非零)。

【讨论】:

  • reddit.com/r/golang/comments/ba387a/…。似乎Scan() 不能处理任意长的行并且它不返回分隔符,当我需要阅读最后一行时,我需要区分它。我理解正确吗?如果两种情况都这样,那么它不适合我的应用程序。
  • 根据该帖子,扫描仪可以扫描(默认情况下)多达 64k 行。此外,您始终可以附加换行字节,所以这不是什么大问题。
【解决方案2】:

您需要使用.StdoutPipe() 将标准输出到阅读器,还需要使用exec.Command(..).Start() 进行增量阅读(.Run() 等待进程退出)。 这是工作代码:

// vim: set noexpandtab tabstop=2:

package main

import (
    "bufio"
    "fmt"
    "io"
    "log"
    "os"
    "os/exec"
)

func main() {
    cmd := exec.Command("seq", "10")
    cmdStdOut, err := cmd.StdoutPipe()
    cmdStdErr, err := cmd.StderrPipe()
    defer cmdStdOut.Close()
    if err != nil {
        log.Fatalf("command failed with %s\n", err)
    }
    stdoutReader := bufio.NewReader(cmdStdOut)
    stderrReader := bufio.NewReader(cmdStdErr)
    err = cmd.Start()
    if err != nil {
        log.Fatalf("cmd.Run() failed with %s\n", err)
    }
    // Read stdout
    for {
        line, err := stdoutReader.ReadBytes('\n')
        if err == io.EOF {
            if len(line) == 0 {
                break
            }
        } else {
            if err != nil {
                log.Fatal(err)
            }
            line = line[:(len(line) - 1)]
        }
        os.Stdout.Write(line)
        os.Stdout.Write([]byte{'\n'})
    }
    // Read stderr
    for {
        line, err := stderrReader.ReadBytes('\n')
        if err == io.EOF {
            if len(line) == 0 {
                break
            }
        } else {
            if err != nil {
                log.Fatal(err)
            }
            line = line[:(len(line) - 1)]
        }
        os.Stderr.Write(line)
        os.Stderr.Write([]byte{'\n'})
    }
    cmd.Wait()
    fmt.Println(cmd.ProcessState.ExitCode())
}

【讨论】:

  • 谢谢。如何捕捉外部程序的退出状态?
  • 您可以Wait() 并阅读ProcessState。我更新了示例。
  • 如何将外部程序的stderr打印到go程序的stderr?
  • 同样的方法,但使用StderrPipe()
猜你喜欢
  • 2011-11-28
  • 1970-01-01
  • 1970-01-01
  • 2010-09-07
  • 1970-01-01
  • 2022-06-30
  • 2018-03-28
相关资源
最近更新 更多