Объединить дочерние процессы stdout и stderr

Как объединить дочерние процессы stdout и stderr?

Следующие действия не выполняются, так как собственность не может быть разделена между stdout и stderr:

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

Другими словами, что такое эквивалент Rust 2>&1 в оболочке?

Ответы

Ответ 1

Мой duct ящик поддерживает это:

#[macro_use]
extern crate duct;

fn main() {
    cmd!("echo", "hi").stderr_to_stdout().run();
}

"Правильный способ" сделать что-то подобное, что duct делает для вас под прикрытием, - это создать двусторонний канал ОС и передать конец его записи как stdout, так и stderr. Класс стандартной библиотеки Command поддерживает подобные вещи в целом, потому что Stdio реализует FromRawFd, но, к сожалению, стандартная библиотека не предоставляет способ создания каналов. Я написал еще один ящик os_pipe, чтобы сделать это внутри duct, и, если хотите, вы можете использовать его напрямую.

Это было проверено на Linux, Windows и macOS.

Ответ 2

Я ничего не вижу в стандартной библиотеке, которая делает это за вас. Не означает, что вы не можете написать это самостоятельно. Это также означает, что вы решаете, как часто читается каждый файловый дескриптор и как объединить данные из каждого дескриптора файла. Здесь я пытаюсь читать фрагменты с использованием размера по умолчанию BufReader и предпочитаю сначала ставить данные 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.

См. также Как префикс команды stdout с помощью [stdout] и [sterr]?


В этом решении нет никакого внутреннего порядка между файловыми дескрипторами. Как аналог, представьте два ведра воды. Если вы опустошите ведро, а затем увидите, что он был заполнен снова, вы знаете, что второе ведро появилось после первого. Однако, если вы опорожняете два ведра и возвращаетесь позже, и оба заполняются, вы не можете определить, какое ведро было заполнено первым.

"Качество" чередования - это вопрос, как часто вы читаете из каждого файлового дескриптора и какой файловый дескриптор читается первым. Если вы читаете по одному байту из каждого в очень сжатом цикле, вы можете получить полностью искаженные результаты, но они будут наиболее "точными" в отношении порядка. Аналогично, если программа выводит "A" на stderr, а затем "B" на stdout, но оболочка считывает из stdout перед stderr, тогда результатом будет "BA", который смотрит назад.