Объединить дочерние процессы 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", который смотрит назад.