Rust 合并子进程stdout和stderr

Rust 合并子进程stdout和stderr,rust,Rust,如何合并子进程stdout和stderr 由于所有权不能在stdout和stderr之间共享,因此以下操作无效: let pipe = Stdio::piped(); let prog = Command::new("prog") .stdout(pipe) .stderr(pipe) .spawn() .

如何合并子进程stdout和stderr

由于所有权不能在
stdout
stderr
之间共享,因此以下操作无效:

let pipe = Stdio::piped();
let prog = Command::new("prog")
                        .stdout(pipe)
                        .stderr(pipe)
                        .spawn()
                        .expect("failed to execute prog");

换句话说,在shell中,与
2>&1
相当的锈迹是什么?

在标准库中,我看不到任何东西可以为您这样做。并不意味着你不能自己写。这还意味着您可以决定读取每个文件描述符的频率,以及如何组合来自每个文件描述符的数据。在这里,我尝试使用默认的
BufReader
size来读取数据块,当两个描述符都有数据时,我更愿意将stdout数据放在第一位

use std::io::prelude::*;
use std::io::BufReader;
use std::process::{Command, Stdio};

fn main() {
    let mut child =
        Command::new("/tmp/output")
        .stdout(Stdio::piped())
        .stderr(Stdio::piped())
        .spawn()
        .expect("Couldn't run program");

    let mut output = Vec::new();

    // Should be moved to a function that accepts something implementing `Write`
    {
        let stdout = child.stdout.as_mut().expect("Wasn't stdout");
        let stderr = child.stderr.as_mut().expect("Wasn't stderr");

        let mut stdout = BufReader::new(stdout);
        let mut stderr = BufReader::new(stderr);

        loop {
            let (stdout_bytes, stderr_bytes) = match (stdout.fill_buf(), stderr.fill_buf()) {
                (Ok(stdout), Ok(stderr)) => {
                    output.write_all(stdout).expect("Couldn't write");
                    output.write_all(stderr).expect("Couldn't write");

                    (stdout.len(), stderr.len())
                }
                other => panic!("Some better error handling here... {:?}", other)
            };

            if stdout_bytes == 0 && stderr_bytes == 0 {
                // Seems less-than-ideal; should be some way of
                // telling if the child has actually exited vs just
                // not outputting anything.
                break;
            }

            stdout.consume(stdout_bytes);
            stderr.consume(stderr_bytes);
        }
    }

    let status = child.wait().expect("Waiting for child failed");
    println!("Finished with status {:?}", status);
    println!("Combined output: {:?}", std::str::from_utf8(&output))
}
最大的差距在于进程何时退出。我对
Child
上缺少相关方法感到惊讶

另见


在这个解决方案中,文件描述符之间没有任何内在的顺序。打个比方,想象两桶水。如果你清空一个桶,然后看到它又被装满了,你就知道第二个桶在第一个桶之后。然而,如果你清空了两个桶,然后回来,两个都装满了,你就不知道是哪个桶先装满的

交错的“质量”取决于您从每个文件描述符读取的频率以及首先读取哪个文件描述符。如果在一个非常紧密的循环中从每个字节中读取一个字节,可能会得到完全混乱的结果,但这些结果在排序方面是最“准确”的。同样,如果一个程序将“a”打印到stderr,然后将“B”打印到stdout,但shell在stderr之前从stdout读取数据,那么结果将是“BA”,向后看。

我的板条箱支持这一点:

#[macro_use]
extern crate duct;

fn main() {
    cmd!("echo", "hi").stderr_to_stdout().run();
}
这样做的“正确方法”是创建一个双端操作系统管道,并将其写入端同时传递给stdout和stderr,而stdout和stderr都是这样做的。标准库的
命令
类通常支持此类操作,因为
Stdio
实现了
FromRawFd
,但不幸的是,标准库没有公开创建管道的方法。我已经编写了另一个名为的板条箱,用于在
风管内执行此操作,如果您需要,可以直接使用它

这已经在Linux、Windows和macOS上进行了测试。

我编写了,以提供一个多端管道式结构;主要用例是从一个进程中捕获stdout和stderr,适当地交错,并区分哪些数据来自哪个数据。有关如何使用它的示例,请参见

(io mux主要在Linux上工作;它也可以在其他UNIX平台上工作,但由于这些平台上UNIX套接字的行为,它有一些限制。)

如果您不关心区分哪些数据来自stdout,哪些数据来自stderr,那么可以使用普通管道。在UNIX上,使用创建管道,两次创建stdout和stderr,生成进程,然后从管道的另一端读取。在Windows上,您可以使用句柄而不是文件描述符来执行类似的操作


如果您不需要区分哪些数据来自stdout,哪些数据来自stderr,并且不想处理设置管道的特定于平台的细节,请尝试以下操作:,其中特别提到了对组合stdout和stderr的支持。

我知道您在问,但为了防止它对任何使用板条箱的人都有帮助,您可以使用:


我一直在寻找类似于
let pipe2=Stdio::from_raw_fd(pipe.to_raw_fd())
的东西,但令人烦恼的是,我在文档中看不到
AsRawFd
的实现。我不确定
Stdio::piped()
是否是您所需要的,但为什么不直接使用
。stdout(Stdio::piped()).stderr(Stdio::pipeed())
?这听起来像是将stdin和stdout重定向到两个单独的管道。OP可能希望将它们重定向到同一管道,而不会丢失它们内容之间的排序信息。例如,等效的Bourne shell构造是
output=$(command 2>&1)
。答案中提供的解决方案与
2>&1
样式重定向不同,因为它丢失了排序信息(如答案中正确描述的)。在不稳定的锈迹中,有一个<代码> BeaEXEXEX/<代码>方法,它可以用来执行<代码>不安全的{LBC::DUP2(1, 2)} /COD>——除了只在UNIX类平台上工作。是的,在一般情况下,排序可能不是完全确定的,而是考虑在子进程中在单个线程上产生所有输出的情况。在这种情况下,程序中所有“printf”之间将有严格的顺序。当printfs打印到不同的管道时,此排序信息将丢失,但如果它们打印到同一管道,则您将始终能够知道消息的打印顺序。
.stderr(Redirection::Merge)