从子进程读取标准输出

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

Reading Stdout from a subprocess

问题

我正在尝试从Golang生成一个子进程。目标是逐行读取和处理输入。以下是我正在尝试使其工作的代码:

func readStuff(scanner *bufio.Scanner) {
    for scanner.Scan() {
        fmt.Println("Performed Scan")
        fmt.Println(scanner.Text())
    }
    if err := scanner.Err(); err != nil {
        fmt.Fprintln(os.Stderr, "reading standard input:", err)
    }
}

func main() {
    cmd := exec.Command("/usr/local/bin/pocketsphinx_continuous", "-inmic", "yes")
    out, err := cmd.StdoutPipe()

    err = cmd.Start()
    checkError(err)

    scanner := bufio.NewScanner(out)
    fmt.Println("Scanner created")

    defer cmd.Wait()
    go readStuff(scanner)
}

在这个例子中,会打印出"Scanner created",但之后没有任何输出。

然而,运行以下命令会产生我期望的输出:

/usr/local/bin/pocketsphinx_continuous -inmic yes 1>out.txt

将代码修改为直接复制到stdout也可以正常工作:

cmd := exec.Command("/usr/local/bin/pocketsphinx_continuous", "-inmic", "yes")
cmd.Stdout = os.Stdout

我错过了什么导致无法读取输出?

英文:

I am attempting to spawn a subprocess from Golang. The goal is to read and process the input line-by-line. Here is what I am trying to get working:

func readStuff(scanner *bufio.Scanner) {
	for scanner.Scan() {
		fmt.Println("Performed Scan")
		fmt.Println(scanner.Text())
	}
	if err := scanner.Err(); err != nil {
		fmt.Fprintln(os.Stderr, "reading standard input:", err)
	}
}

func main() {
	cmd := exec.Command("/usr/local/bin/pocketsphinx_continuous", "-inmic", "yes")
	out, err := cmd.StdoutPipe()

	err = cmd.Start()
	checkError(err)

	scanner := bufio.NewScanner(out)
	fmt.Println("Scanner created")

	defer cmd.Wait()
	go readStuff(scanner)
}

In this example, "Scanner created" is printed, but nothing happens after that.

Running this command however does result in what I am expecting to be printed to :

/usr/local/bin/pocketsphinx_continuous -inmic yes 1>out.txt

And modifying the code to directly copy to stdout works as well:

cmd := exec.Command("/usr/local/bin/pocketsphinx_continuous", "-inmic", "yes")
cmd.Stdout = os.Stdout

What am I missing that is keeping me from reading the output?

答案1

得分: 6

有几个事情你可能想要检查。

  1. 没有检查 cmd.StdoutPipe() 返回的错误代码。应该进行检查。

  2. pocketsphinx_continuous 命令需要提供 -hmm-dict 参数。否则,它将失败,并且所有的输出实际上都被发送到 stderr 而不是 stdout。在这里,你只读取了 stdout,但没有内容可读取。

  3. 在确定从 stdout 中读取了所有数据之前,不应该调用 cmd.Wait()。结果是不确定的(实际上,这是一种竞争条件)。请查阅关于 os/exec 包的文档。如果你绝对需要在 goroutine 中进行解析,你需要在调用 cmd.Wait() 之前与 goroutine 的结束进行同步。例如,你可以将函数写成:

     func readStuff(scanner *bufio.Scanner, stop chan bool) {
         // 扫描代码
         // ...
         stop <- true
     }
    

并将主要代码写成:

    stop := make(chan bool)
    go readStuff(scanner, stop)
    <-stop
    cmd.Wait()
英文:

There are multiple things you may want to check.

  1. The error code returned by cmd.StdoutPipe() is not checked. It should be.

  2. The pocketsphinx_continuous command requires the -hmm and -dict arguments to be provided. Otherwise, it will fail, and all the output is actually sent to stderr and not stdout. Here, you read only stdout, but there is nothing to read.

  3. You should not call cmd.Wait() before being sure all the data have been read from stdout. The result is non deterministic (actually, it is a race condition). Check the documentation about the os/exec package. If you absolutely need the parsing to be done in a goroutine, you need to synchronize with the end of the goroutine before cmd.Wait() is called. For instance, you could write the function as:

     func readStuff(scanner *bufio.Scanner, stop chan bool) {
         // Scanning code
         // ...
         stop&lt;-true
     }
    

and the main code as:

    stop := make(chan bool)
    go readStuff(scanner,stop)
    &lt;-stop
    cmd.Wait()

答案2

得分: 1

看起来你不需要执行go readStuff(scanner),因为cmd.Start()会自动创建一个系统进程。所以我认为只需要执行readStuff(scanner)就足够了,不需要为此创建一个goroutine。

英文:

Seems you need not

go readStuff(scanner)

because of

cmd.Start()

do system fork itself
so just

readStuff(scanner)

would be enough to my mind
(not spawning gorouting for that)

答案3

得分: 1

这似乎工作得很好,并且可以使用go readStuff(scanner)或者只是readStuff(scanner)来调用。我认为这种用法实际上并不需要使用goroutine,但是不知道实际的上下文。

package main

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

func readStuff(scanner *bufio.Scanner) {
	for scanner.Scan() {
		fmt.Println("Performed Scan")
		fmt.Println(scanner.Text())
	}
	if err := scanner.Err(); err != nil {
		fmt.Fprintln(os.Stderr, "reading standard input:", err)
	}
}

func main() {
	cmd := exec.Command("/Users/rfay/bin/junk.sh")
	out, err := cmd.StdoutPipe()

	err = cmd.Start()
	if err != nil {
		fmt.Fprintf(os.Stderr, "Failed to start err=%v", err)
		os.Exit(1)
	}

	scanner := bufio.NewScanner(out)
	fmt.Println("Scanner created")

	defer cmd.Wait()

	go readStuff(scanner)
}

这是我用于测试的junk.sh脚本。

#!/bin/bash
for i in `seq 1 10`;
do
  echo $i
  sleep 1
done
英文:

This seems to work fine, and it works with go readStuff(scanner) and also with just readStuff(scanner) - I don't think that this usage actually calls for a goroutine, but don't know the actual context.:

package main

import (
	&quot;bufio&quot;
	&quot;fmt&quot;
	&quot;os&quot;
	&quot;os/exec&quot;
)

func readStuff(scanner *bufio.Scanner) {
	for scanner.Scan() {
		fmt.Println(&quot;Performed Scan&quot;)
		fmt.Println(scanner.Text())
	}
	if err := scanner.Err(); err != nil {
		fmt.Fprintln(os.Stderr, &quot;reading standard input:&quot;, err)
	}
}

func main() {
	cmd := exec.Command(&quot;/Users/rfay/bin/junk.sh&quot;)
	out, err := cmd.StdoutPipe()

	err = cmd.Start()
	if err != nil {
		fmt.Fprintf(os.Stderr, &quot;Failed to start err=%v&quot;, err)
		os.Exit(1)
	}

	scanner := bufio.NewScanner(out)
	fmt.Println(&quot;Scanner created&quot;)

	defer cmd.Wait()

	go readStuff(scanner)
}

This is the junk.sh I used for testing.

#!/bin/bash
for i in `seq 1 10`;
do
  echo $i
  sleep 1
done    

huangapple
  • 本文由 发表于 2014年12月20日 12:24:48
  • 转载请务必保留本文链接:https://go.coder-hub.com/27576902.html
匿名

发表评论

匿名网友

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

确定