首页 > 解决方案 > 为什么从 stderr 读取时 BufReader 挂起?

问题描述

我想执行一个命令,然后将任何潜在的输出捕获到 stderr。这是我所拥有的:

if let Ok(ref mut child) = Command::new("ssh")
            .args(&[
              "some_args",
              "more_args"
            ])
            .stderr(Stdio::piped())
            .spawn()
        {
            let output = child.wait().expect("ssh command not running");
            let reader = BufReader::new(child.stderr.take().expect("failed to capture stderr"));
            for line in reader.lines() {
                match line {
                    Ok(line_str) => println!("output: {}", line_str);
                    Err(e) => println!("output failed!"),
                }
            }
        }

我看到正在打印输出,但程序随后挂起。我怀疑这可能与子进程退出和 BufReader 无法读取 eof 有关。一种解决方法是维护一个let mut num_lines = 0;,然后在每次读取时增加它。在 x 次读取后,我中断了 for 循环,但这似乎不是很干净。如何让 BufReader 正确完成阅读?

标签: rust

解决方案


这些都不能解决您的问题,但无论如何我都会提供建议:

Pipe-Wait-Read 可能会死锁

调用child.wait()将阻止执行,直到孩子退出,返回退出状态。

使用Stdio::piped()为 stdout/stderr 流创建一个新管道,以便由应用程序处理。管道由操作系统处理,不是无限的;如果管道的一端正在写入数据但另一端没有读取它,它最终会阻止这些写入,直到读取某些内容。

此代码可能会死锁,因为您正在等待子进程退出,但如果它被阻止尝试写入已满且未被读取的输出管道,则它可能无法死锁。

例如,我的系统(一个相当标准的 ubuntu 系统,有 64KiB 的管道缓冲区)上的死锁:

// create a simple child proccess that sends 64KiB+1 random bytes to stdout
let mut child = Command::new("dd")
    .args(&["if=/dev/urandom", "count=65537", "bs=1", "status=none"])
    .stdout(Stdio::piped())
    .spawn()
    .expect("failed to execute dd");

let _status = child.wait(); // hangs indefinitely
let reader = BufReader::new(child.stdout.take().expect("failed to capture stdout"));
for _line in reader.lines() {
    // do something
}

有很多选择:

  • 无需等待即可读取输出。reader.lines()当它到达流的末尾时将停止迭代。child.wait()如果你想知道退出状态,你可以打电话。

  • 使用.output()而不是.spawn(). 这将阻塞,直到孩子退出并返回一个Output持有完整的 stdout/stderr 流作为Vec<u8>s。

  • 在等待子进程退出时,您可以在单独的线程中处理输出流。如果这听起来不错,请考虑使用tokio::process::Command.

请参阅如何在 Rust 中不阻塞地读取子进程的输出?了解更多信息。

不要吞下错误.lines()

reader.lines()返回一个迭代器,它为每一行产生一个结果。可以在一定程度上处理的错误状态之一是,如果该行未正确 utf-8 编码,它将返回如下内容:

Err(
    Custom {
        kind: InvalidData,
        error: "stream did not contain valid UTF-8",
    },
)

但是,任何其他错误都将直接来自底层阅读器,您可能应该继续迭代。您收到的任何错误都不太可能恢复,当然也不能通过继续要求更多行来恢复。


推荐阅读