无法通过管道多次传入或传出生成的 child 进程

Unable to pipe to or from spawned child process more than once

我希望能够使用 Rust 生成一个 child shell,然后重复向它传递任意命令并处理它们的输出。我在网上找到了很多示例,向我展示了如何传递单个命令并接收其单个输出,但我似乎无法重复执行此操作。

比如下面的代码在注释之后挂在了一行。 (我想 read_to_string() 可能会阻塞,直到它从 child 进程接收到标准输出,但如果是这样,我不明白为什么输出不会出现......)

let mut child_shell = match Command::new("/bin/bash")
    .stdin(Stdio::piped())
    .stdout(Stdio::piped())
    .spawn()
{
    Err(why) => panic!("couldn't spawn child_shell: {}", Error::description(&why)),
    Ok(process) => process,
};

loop {
    {
        match child_shell.stdin.as_mut().unwrap().write("ls".as_bytes()) {
            Err(why) => panic!(
                "couldn't send command to child shell: {}",
                Error::description(&why)
            ),
            Ok(_) => println!("sent command to child shell"),
        }
    }

    {
        let mut s = String::new();
        // ↓ hangs on this line ↓
        match child_shell.stdout.as_mut().unwrap().read_to_string(&mut s) {
            Err(why) => panic!("couldn't read bash stdout: {}", Error::description(&why)),
            Ok(_) => print!("bash responded with:\n{}", s),
        }
    }
}

我是 Rust 的初学者,我认为问题是我对 borrow-checker/referencing 规则的理解有限,因为如果我删除上面的 运行s 很好(对于单次迭代)代码中的循环指令并将对 std::process::Child 结构的内部结构的引用更改为不可变;例如来自:

child_shell.stdin.as_mut().unwrap().write("ls".as_bytes())

对此:

 child_shell.stdin.unwrap().write("ls".as_bytes())

显然,重复 运行 宁 ls 不是我的最终目标,我知道我可以只写一个 shell 脚本然后让 Rust 重复 运行 它 - 但是(除了学习更多关于 Rust 的目标之外!)这是我需要能够做的事情,至少在原则上,对于一个更复杂的项目(如果可能的话,我很乐意参与)证明与任何解决方案相关,但它可能超出了这个问题的范围!)

最后,如果事实证明无法以这种方式使用 child shell,我仍然想学习如何 repeatedly/continuously 管道到并从衍生进程 运行 宁一些其他任意命令,因为我无法在 Rust 文档、教程或 Stack Overflow 中找到任何信息。

read_to_string 记录为

Read all bytes until EOF in this source

因此,它一直在等待,直到所有输入完成,这在 shell 关闭之前永远不会发生。您可以通过从输出中读取一定数量的数据来解决此问题。这是一个示例,其中我删除了您必须显示解决方案核心的所有漂亮错误打印:

use std::process::{Command, Stdio};
use std::io::{BufRead, Write, BufReader};

fn main() {
    let mut child_shell = Command::new("/bin/bash")
        .stdin(Stdio::piped())
        .stdout(Stdio::piped())
        .spawn()
        .unwrap();

    let child_in = child_shell.stdin.as_mut().unwrap();
    let mut child_out = BufReader::new(child_shell.stdout.as_mut().unwrap());
    let mut line = String::new();

    loop {
        child_in.write("ls\n".as_bytes()).unwrap();
        child_out.read_line(&mut line).unwrap();
        println!("{}", line);
    }
}

在这里,我们使用 BufRead 特征来允许从输入中读取,直到我们读取一行值。然后我们打印出来并继续我们的循环。当然,每行输入不止一行输出,所以等待读取的内容只会越来越多。

在你的真实代码中,你需要弄清楚什么时候停止阅读。如果您有固定大小的响应,这可能真的很容易,或者如果您正在尝试处理人机交互程序,这可能会非常困难。

请注意直接使用 child_shell.stdinstdout,不要使用 Option::as_refOption::as_mutOption::take。直接使用 stdinstdout 会将该项移出 Child 结构,使 Child 部分有效。例如,您将无法再对其调用 waitkill

顺便提一下,您不需要调用 Error::description(&why) 之类的特征方法。你可以直接说 why.description().