Go:将 os/exec 命令输出写入多个文件会破坏上下文。WithTimeout

问题描述 投票:0回答:1

我想将

os/exec
命令的输出写入多个文件。从我在网上发现的情况来看,io.MultiWriter 似乎是完成这项工作的正确工具。我测试了它(请参阅下面的示例代码),它确实将输出写入多个文件。

multiWriter := io.MultiWriter(logFile1, logFile2)
ctx, cancel := context.WithTimeout(ctx, timeout)
defer cancel()
cmd := exec.CommandContext(ctx, "bash", "-c", "command")
cmd.SysProcAttr = &syscall.SysProcAttr{Setpgid: true}
cmd.Stdout = multiWriter
cmd.Stderr = multiWriter

cmd.Start()
cmd.Wait()

我遇到的问题是,如果存在超时(由

ctx
定义),cmd.Wait() 命令不会按预期中断。根据调试器的说法,它挂起,等待来自
https://go.dev/src/os/exec/exec.go
. 第 957 行 return <-c.goroutineErr
的消息。 当我将
*os.File
变量直接设置为
cmd.Stdout
cmd.Stderr
时,
c.goroutineErr
chan 为零,并且对
cmd.Wait()
的调用按预期中断。

来自 os/exec 文档:

// Stdout and Stderr specify the process's standard output and error.
//
// If either is nil, Run connects the corresponding file descriptor
// to the null device (os.DevNull).
//
// If either is an *os.File, the corresponding output from the process
// is connected directly to that file.
//
// Otherwise, during the execution of the command a separate goroutine
// reads from the process over a pipe and delivers that data to the
// corresponding Writer. In this case, Wait does not complete until the
// goroutine reaches EOF or encounters an error or a nonzero WaitDelay
// expires.
//
// If Stdout and Stderr are the same writer, and have a type that can
// be compared with ==, at most one goroutine at a time will call Write.

向命令添加 WaitDelay 的正确方法是吗?

go
1个回答
0
投票

难道你的程序正在抵抗被杀死吗?它是以

root
或其他方式运行吗?

以下代码对我来说效果很好(在 Linux 上)。两个文件:

#!/bin/bash
#           Call this "drip.sh"
let x=0
while true; do
    let x=${x}+1
    echo "drip ${x}"
    sleep 1
done

和一些 Go 代码:

// Program timeout (timeout.go).
package main

import (
    "bytes"
    "context"
    "fmt"
    "io"
    "os/exec"
    "syscall"
    "time"
)

func main() {
    timeout := 5 * time.Second
    ctx := context.Background()
    var logFile1, logFile2 bytes.Buffer

    multiWriter := io.MultiWriter(&logFile1, &logFile2)
    ctx, cancel := context.WithTimeout(ctx, timeout)
    defer cancel()

    cmd := exec.CommandContext(ctx, "bash", "-c", "./drip.sh")
    cmd.SysProcAttr = &syscall.SysProcAttr{Setpgid: true}
    cmd.Stdout = multiWriter
    cmd.Stderr = multiWriter

    cmd.Start()
    cmd.Wait()

    fmt.Println("program exited")
    fmt.Printf("log1: %q\n", logFile1.String())
    fmt.Printf("log2: %q\n", logFile2.String())
}

运行它(等待编译和程序超时):

$ go run timeout.go
program exited
log1: "drip 1\ndrip 2\ndrip 3\ndrip 4\ndrip 5\n"
log2: "drip 1\ndrip 2\ndrip 3\ndrip 4\ndrip 5\n"

根据系统负载,您可能会得到 4,5 或 6 行输出。

© www.soinside.com 2019 - 2024. All rights reserved.