![](/img/trans.png)
[英]How can I parse output from exec.Command while still logging in realtime to os.Stdout?
[英]Capture stdout from exec.Command line by line and also pipe to os.Stdout
任何人都可以帮忙吗?
我有一个通过 exec.CommandContext 运行的应用程序(所以我可以通过 ctx 取消它)。 它通常不会停止,除非它出错。
我目前将其输出中继到 os.stdOut ,效果很好。 但我也想通过通道获取每一行 - 这背后的想法是我将在该行上查找正则表达式,如果它为真,那么我将设置一个“错误”的内部状态,例如。
虽然我无法让它工作,但我尝试了 NewSscanner。 这是我的代码。
正如我所说,它确实输出到 os.StdOut,这很棒,但我希望在我设置的频道中接收每一行。
有任何想法吗 ?
提前致谢。
func (d *Daemon) Start() {
ctx, cancel := context.WithCancel(context.Background())
d.cancel = cancel
go func() {
args := "-x f -a 1"
cmd := exec.CommandContext(ctx, "mydaemon", strings.Split(args, " ")...)
var stdoutBuf, stderrBuf bytes.Buffer
cmd.Stdout = io.MultiWriter(os.Stdout, &stdoutBuf)
cmd.Stderr = io.MultiWriter(os.Stderr, &stderrBuf)
lines := make(chan string)
go func() {
scanner := bufio.NewScanner(os.Stdin)
for scanner.Scan() {
fmt.Println("I am reading a line!")
lines <- scanner.Text()
}
}()
err := cmd.Start()
if err != nil {
log.Fatal(err)
}
select {
case outputx := <-lines:
// I will do somethign with this!
fmt.Println("Hello!!", outputx)
case <-ctx.Done():
log.Println("I am done!, probably cancelled!")
}
}()
}
也尝试使用这个
go func() {
scanner := bufio.NewScanner(&stdoutBuf)
for scanner.Scan() {
fmt.Println("I am reading a line!")
lines <- scanner.Text()
}
}()
即便如此,“我正在阅读一行”永远不会出来,我也调试了它,它从来没有进入“扫描仪..”
还尝试扫描&stderrBuf
,同样,没有进入。
cmd.Start()
不会等待命令完成。 此外,需要调用cmd.Wait()
以获知进程结束。
reader, writer := io.Pipe()
cmdCtx, cmdDone := context.WithCancel(context.Background())
scannerStopped := make(chan struct{})
go func() {
defer close(scannerStopped)
scanner := bufio.NewScanner(reader)
for scanner.Scan() {
fmt.Println(scanner.Text())
}
}()
cmd := exec.Command("ls")
cmd.Stdout = writer
_ = cmd.Start()
go func() {
_ = cmd.Wait()
cmdDone()
writer.Close()
}()
<-cmdCtx.Done()
<-scannerStopped
增加了scannerStopped
来演示scanner goroutine 现在停止了。
reader, writer := io.Pipe()
scannerStopped := make(chan struct{})
go func() {
defer close(scannerStopped)
scanner := bufio.NewScanner(reader)
for scanner.Scan() {
fmt.Println(scanner.Text())
}
}()
cmd := exec.Command("ls")
cmd.Stdout = writer
_ = cmd.Run()
go func() {
_ = cmd.Wait()
writer.Close()
}()
<-scannerStopped
并在有帮助时处理线条。
注意:写这个有点匆忙。 如果有任何不清楚或不正确的地方,请告诉我。
你必须扫描stdoutBuf
而不是os.Stdin
:
scanner := bufio.NewScanner(&stdoutBuf)
当上下文取消时,命令终止。 如果在命令终止之前可以读取命令的所有输出,请使用以下代码:
func (d *Daemon) Start() {
ctx, cancel := context.WithCancel(context.Background())
d.cancel = cancel
args := "-x f -a 1"
cmd := exec.CommandContext(ctx, "mydaemon", strings.Split(args, " ")...)
stdout, err := cmd.StdoutPipe()
if err != nil {
log.Fatal(err)
}
err = cmd.Start()
if err != nil {
log.Fatal(err)
}
go func() {
defer cmd.Wait()
scanner := bufio.NewScanner(stdout)
for scanner.Scan() {
s := scanner.Text()
fmt.Println(s) // echo to stdout
// Do something with s
}
}()
}
当上下文被取消时,命令终止。
当命令终止时,读取stdout
返回 io.EOF。 当stdout
返回错误时,goroutine 退出扫描循环。
对于一个正确的使用并发和goroutines的程序,我们至少应该确保没有数据竞争,程序不能死锁,goroutines不会泄漏。
游乐场: https : //play.golang.org/p/VDW5rj1DltG 。 我建议在本地复制和运行,因为操场不会流输出 afaik 并且它有超时。
请注意,我已将测试命令更改为% find /usr/local
,这是一个典型的长时间运行命令(> 3 秒),具有大量输出行,因为它更适合我们应该测试的场景。
让我们看看Daemon.Start
方法。 最值得注意的是,新代码没有在Daemon.Start
代码的大部分周围使用 goroutine。 即使没有这个, Daemon.Start
方法仍然是非阻塞的并且会立即返回。
第一个值得注意的修复是这些更新的行。
outR, outW := io.Pipe()
cmd.Stdout = io.MultiWriter(outW, os.Stdout)
我们没有构造 bytes.Buffer 变量,而是调用io.Pipe
。 如果我们没有进行此更改并坚持使用 bytes.Buffer,那么一旦没有更多数据可读取,scanner.Scan scanner.Scan()
将返回 false。 如果命令只是偶尔写入 stdout(对于这个问题,即使相隔一毫秒),就会发生这种情况。 在scanner.Scan()
返回false 后,goroutine 退出并且我们错过了处理未来的输出。
通过使用io.Pipe
的读取端, io.Pipe
scanner.Scan()
将等待来自管道读取端的输入,直到管道的写入端关闭。
这解决了扫描仪和命令输出之间的竞争问题。
接下来,我们构造两个密切相关的 goroutine:第一个从<-lines
消费,第二个从生产到lines<-
。
go func() {
for line := range lines {
fmt.Println("output line from channel:", line)
...
}
}()
go func() {
defer close(lines)
scanner := bufio.NewScanner(outR)
for scanner.Scan() {
lines <- scanner.Text()
}
...
}()
当lines
channel关闭时consumer goroutine会退出,因为channel的关闭自然会导致range loop终止; 生产者 goroutine 在退出时关闭lines
。
当scanner.Scan()
返回false 时,生产者goroutine 将退出,这发生在io.Pipe
的写端关闭时。 这种关闭发生在即将到来的代码中。
注意上面两段,两个goroutines保证退出(即不会泄漏)。
接下来,我们启动命令。 标准的东西,它是一个非阻塞调用,它立即返回。
// Start the command.
if err := cmd.Start(); err != nil {
log.Fatal(err)
}
转到Daemon.Start
的最后一段代码。 这个 goroutine 通过cmd.Wait()
等待命令退出。 处理这一点很重要,因为该命令可能出于上下文取消以外的原因。
特别是,我们想要关闭io.Pipe
的写端(反过来, io.Pipe
,它会关闭输出行生产者 goroutine)。
go func() {
err := cmd.Wait()
fmt.Println("command exited; error is:", err)
outW.Close()
...
}()
这个 goroutine 也保证退出。 当cmd.Wait()
返回时它将退出。 这可能是因为命令成功正常退出; 由于命令错误而失败退出; 或由于上下文取消而失败退出。
就是这样! 我们应该没有数据竞争,没有死锁,也没有泄露的 goroutine。
上面代码片段中省略的行 (" ...
") 适用于 Daemon 类型的Done()
、 CmdErr()
和Cancel()
方法。 这些方法在代码中得到了很好的记录,因此这些省略的行希望是不言自明的。
除此之外,根据您的需要查找您可能想要做的错误处理的TODO
注释!
使用此驱动程序来测试代码。
func main() {
var d Daemon
d.Start()
// Enable this code to test Context cancellation:
// time.AfterFunc(100*time.Millisecond, d.Cancel)
<-d.Done()
fmt.Println("d.CmdErr():", d.CmdErr())
}
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.