在Golang中运行外部Python代码,捕获连续的exec.Command标准输出。

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

Running external python in Golang, Catching continuous exec.Command Stdout

问题

你可以使用管道(Pipe)来实现并发地捕获Python脚本的输出并打印到Go语言的终端上。以下是修改后的代码示例:

cmd := exec.Command("python", "game.py")

// 创建管道
stdout, err := cmd.StdoutPipe()
if err != nil {
    panic(err)
}

// 启动Python脚本
err = cmd.Start()
if err != nil {
    panic(err)
}

// 并发地读取并打印Python脚本的输出
go func() {
    scanner := bufio.NewScanner(stdout)
    for scanner.Scan() {
        fmt.Println(scanner.Text())
    }
}()

// 等待Python脚本执行完毕
err = cmd.Wait()
if err != nil {
    panic(err)
}

这样,你就可以同时运行Python脚本并实时捕获其输出,然后在Go语言的终端上打印出来。

英文:

So my go script will call an external python like this

cmd = exec.Command("python","game.py")
cmd.Stdout = os.Stdout
cmd.Stderr = os.Stderr
go func(){
	err := cmd.Run()
	if err != nil{
	panic(err)
	}
}()

It runs my python script concurrently which is awesome.
But now the problem is, my python script will run infinitely and it will print out some information from time to time. I want to "catch" these Stdout and print them out on my golang terminal. How do I do it concurrently (without waiting my python script to exit)?

答案1

得分: 7

使用cmd.Start()cmd.Wait()代替cmd.Run()

https://golang.org/pkg/os/exec/#Cmd.Run

Run启动指定的命令并等待其完成。

Start启动指定的命令,但不等待其完成。

Wait等待命令退出。它必须由Start启动。

如果你想同时捕获stdout/stderr,可以使用cmd.StdoutPipe() / cmd.StderrPipe(),并通过bufio.NewScanner()读取。

package main

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

func main() {
    cmd := exec.Command("python", "game.py")
    stdout, err := cmd.StdoutPipe()
    if err != nil {
        panic(err)
    }
    stderr, err := cmd.StderrPipe()
    if err != nil {
        panic(err)
    }
    err = cmd.Start()
    if err != nil {
        panic(err)
    }

    go copyOutput(stdout)
    go copyOutput(stderr)
    cmd.Wait()
}

func copyOutput(r io.Reader) {
    scanner := bufio.NewScanner(r)
    for scanner.Scan() {
        fmt.Println(scanner.Text())
    }
}

以下是一个用于重现实时输出的示例Python代码。stdout在Python中可能会被缓冲。可能需要显式刷新。

import time
import sys

while True:
    print("Hello")
    sys.stdout.flush()
    time.sleep(1)
英文:

Use cmd.Start() and cmd.Wait() instead of cmd.Run().

https://golang.org/pkg/os/exec/#Cmd.Run

> Run starts the specified command and waits for it to complete.
>
> Start starts the specified command but does not wait for it to complete.
>
> Wait waits for the command to exit. It must have been started by Start.

And if you want to capture stdout/stderr concurrently, use cmd.StdoutPipe() / cmd.StderrPipe() and read it by bufio.NewScanner()

package main

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

func main() {
    cmd := exec.Command("python", "game.py")
    stdout, err := cmd.StdoutPipe()
    if err != nil {
        panic(err)
    }
    stderr, err := cmd.StderrPipe()
    if err != nil {
        panic(err)
    }
    err = cmd.Start()
    if err != nil {
        panic(err)
    }

    go copyOutput(stdout)
    go copyOutput(stderr)
    cmd.Wait()
}

func copyOutput(r io.Reader) {
    scanner := bufio.NewScanner(r)
    for scanner.Scan() {
        fmt.Println(scanner.Text())
    }
}

The following is a sample python code for reproducing real-time output. The stdout may be buffered in Python. Explicit flush may be required.

import time
import sys

while True:
    print "Hello"
    sys.stdout.flush()
    time.sleep(1)

答案2

得分: 1

如果你想同时捕获标准输出和标准错误输出,确实需要使用cmd.StdoutPipe()cmd.StderrPipe()cmd.Start()cmd.Wait()

但正如文档中提到的(https://pkg.go.dev/os/exec#Cmd.StdoutPipe):

> 因此,在所有从管道中读取完成之前调用Wait是不正确的。

在调用cmd.Wait()之前,你需要等待你的goroutine完成(参见以下线程:https://github.com/golang/go/issues/38268)。

英文:

If you want to capture stdout and stderr concurrently, indeed you need to use cmd.StdoutPipe(), cmd.StderrPipe(), cmd.Start() and cmd.Wait().

But as mentioned in the documentation (https://pkg.go.dev/os/exec#Cmd.StdoutPipe):

> It is thus incorrect to call Wait before all reads from the pipe have completed.

You'll need to wait for you goroutines to finish before calling cmd.Wait() (see the following thread: https://github.com/golang/go/issues/38268)

答案3

得分: 0

你可以使用添加标志"-u"来关闭缓冲:

cmd := exec.Command("python", "-u", "game.py")

https://docs.python.org/3/using/cmdline.html#cmdoption-u

https://bugs.python.org/issue526382

英文:

You can use add the flag -u to turn off buffering,

cmd := exec.Command("python", "-u", "game.py")

https://docs.python.org/3/using/cmdline.html#cmdoption-u

https://bugs.python.org/issue526382

huangapple
  • 本文由 发表于 2017年1月1日 20:09:47
  • 转载请务必保留本文链接:https://go.coder-hub.com/41415337.html
匿名

发表评论

匿名网友

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

确定