How can I redirect the stdout and stderr of a command to both the console and a log file while outputting in real time?

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

How can I redirect the stdout and stderr of a command to both the console and a log file while outputting in real time?

问题

以下代码片段完全符合我的要求,但它只打印到控制台。

cmd := exec.Command("php", "randomcommand.php")

cmd.Stdout = os.Stdout
cmd.Stderr = os.Stderr

if err := cmd.Run(); err != nil {
    log.Fatal(err)
}

randomcommand.php:

// randomcommand.php只是在stdout和stderr之间交替输出20次

$stdout = fopen('php://stdout', 'w+');
$stderr = fopen('php://stderr', 'w+');
for ($i = 1; $i <= 20; $i++) {
    fwrite($stdout, "stdout $i\n");
    fwrite($stderr, "stderr $i\n");
}

输出:

stdout 1
stderr 1
stdout 2
stderr 2
stdout 3
stderr 3
stdout 4
stderr 4
stdout 5
stderr 5
stdout 6
stderr 6
stdout 7
stderr 7
stdout 8
stderr 8
stdout 9
stderr 9
stdout 10
stderr 10
stdout 11
stderr 11
stdout 12
stderr 12
stdout 13
stderr 13
stdout 14
stderr 14
stdout 15
stderr 15
stdout 16
stderr 16
stdout 17
stderr 17
stdout 18
stderr 18
stdout 19
stderr 19
stdout 20
stderr 20

我想要实现以下目标:

  1. 实时将命令的stdout和stderr打印到控制台;
  2. 将命令的stdout和stderr记录到文件中,与在控制台中显示的完全一样;
  3. 保持对stdout和stderr的写入顺序的准确性;
  4. 不修改命令本身。

我尝试过将命令的stdout和stderr导入到扫描器或使用io.Copy,每个都在一个goroutine中,但输出的顺序没有保持。可能是因为在goroutine之间交替发生的同时,输出也在交替,这几乎是不可能的。

我还尝试了在这里的示例中使用"select"。但输出的顺序没有保持。

使用Logstreamer也存在同样的问题。

我另一个想法是尝试看看是否可以从控制台缓冲区中读取,但这种方法并不正确。

有人知道这是否可能并且值得追求吗?

英文:

The following bit of code does exactly what I want, except it only prints to the console.

cmd := exec.Command(&quot;php&quot;, &quot;randomcommand.php&quot;)

cmd.Stdout = os.Stdout
cmd.Stderr = os.Stderr

if err := cmd.Run(); err != nil {
	log.Fatal(err)
}

randomcommand.php:

// randomcommand.php simply alternates output between stdout and stderr 20 times

$stdout = fopen(&#39;php://stdout&#39;, &#39;w+&#39;);
$stderr = fopen(&#39;php://stderr&#39;, &#39;w+&#39;);
for ($i = 1;$i &lt;= 20; $i++) {
    fwrite($stdout, &quot;stdout $i\n&quot;);
    fwrite($stderr, &quot;stderr $i\n&quot;);
}

Output:

stdout 1
stderr 1
stdout 2
stderr 2
stdout 3
stderr 3
stdout 4
stderr 4
stdout 5
stderr 5
stdout 6
stderr 6
stdout 7
stderr 7
stdout 8
stderr 8
stdout 9
stderr 9
stdout 10
stderr 10
stdout 11
stderr 11
stdout 12
stderr 12
stdout 13
stderr 13
stdout 14
stderr 14
stdout 15
stderr 15
stdout 16
stderr 16
stdout 17
stderr 17
stdout 18
stderr 18
stdout 19
stderr 19
stdout 20
stderr 20

What I'm trying to achieve is:

  1. Print the command's stdout and stderr to the console in real-time;
  2. Log the command's stdout and stderr to a file, exactly as it appears in the console;
  3. Respect the exact order of writes to stdout and stderr, and;
  4. Not modify the command itself

I've tried piping the command stdout and stderr into scanners or using io.Copy, each in a goroutine, but order of the output was not maintained. Probably because alternating between goroutines would have to occur at the same time as output alternates, which is just about impossible.

I also tried the using "select" as in the examples from here. But order of output was not maintained.

Same problem with Logstreamer.

The other idea I have to try is to see if I can somehow just read from the console buffer, but this just doesn't feel right.

Does anyone know if this is even possible and worth pursuing?

答案1

得分: 21

如我在评论部分所说,可以使用MultiWriter来实现。

package main

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

func main() {
	// 日志功能
	f, err := os.OpenFile("log.log", os.O_RDWR|os.O_CREATE|os.O_APPEND, 0666)
	if err != nil {
		log.Fatalf("打开文件出错: %v", err)
	}
	defer f.Close()
	mwriter := io.MultiWriter(f, os.Stdout)
	cmd := exec.Command("ls")
	cmd.Stderr = mwriter
	cmd.Stdout = mwriter
	err = cmd.Run() // 阻塞直到子进程完成
	if err != nil {
		panic(err)
	}
}

在声明命令并运行之前,只需指定Stdout和Stderr使用上面定义的MultiWriter。这个MultiWriter实例包含了一个日志文件和标准输出。

英文:

As I said in the comment section, this can be achieved using MultiWriter

package main

import (
	&quot;io&quot;
	&quot;log&quot;
	&quot;os&quot;
	&quot;os/exec&quot;
)

func main() {
	// Logging capability
	f, err := os.OpenFile(&quot;log.log&quot;, os.O_RDWR|os.O_CREATE|os.O_APPEND, 0666)
	if err != nil {
		log.Fatalf(&quot;Error opening file: %v&quot;, err)
	}
	defer f.Close()
	mwriter := io.MultiWriter(f, os.Stdout)
	cmd := exec.Command(&quot;ls&quot;)
	cmd.Stderr = mwriter
	cmd.Stdout = mwriter
	err = cmd.Run() //blocks until sub process is complete
	if err != nil {
		panic(err)
	}
}

When you declare your command, and before you run it, just specify that Stdout and Stderr are using the MultiWriter defined above. This MultiWriter instance contains both a log file and the standard output.

huangapple
  • 本文由 发表于 2015年6月23日 12:11:16
  • 转载请务必保留本文链接:https://go.coder-hub.com/30993717.html
匿名

发表评论

匿名网友

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

确定