无法多次通过管道传入或传出生成的子进程 [英] Unable to pipe to or from spawned child process more than once

查看:35
本文介绍了无法多次通过管道传入或传出生成的子进程的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

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

I want to be able to use Rust to spawn a child shell, then repeatedly pass it arbitrary commands and process their outputs. I have found plenty of examples online showing me how to pass a single command and receive its single output, but I can't seem to be able to do it repeatedly.

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

For instance, the following code hangs on the line after the comment. (I imagine maybe read_to_string() is blocking until it receives stdout from the child process, but if so I don't understand why that output isn't forthcoming..)

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:
{}", s),
        }
    }
}

我是 Rust 的初学者,我认为问题在于我对借用检查器/引用规则的理解有限,因为如果我从代码中删除循环指令并更改,上述运行良好(对于单次迭代)std::process::Child 结构内部对不可变的引用;例如从这个:

I'm a beginner in Rust and I think the problem is my limited understanding of the borrow-checker/referencing rules, since the above runs fine (for a single iteration) if I remove the loop instruction from the code and change the references to the innards of the std::process::Child struct to immutable; for instance from this:

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

到这里:

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

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

Obviously, repeatedly running ls isn't my ultimate goal, and I know that I could just write a shell script and then have Rust repeatedly run it - but (apart from the goal of just learning more about Rust!) this is something I need to be able to do, at least in principle, for a more complicated project (which I'm happy to go into if it might prove relevant to any solutions, but it's probably way, way outside the scope of this question!)

最后,如果事实证明无法以这种方式使用子 shell,我仍然想学习如何重复/连续地管道进出运行其他任意命令的衍生进程,如我无法在 Rust 文档、教程或 Stack Overflow 上找到任何信息.

Finally, if it turns out that it isn't possible to use a child shell in this way, I'd nevertheless like to learn how to repeatedly/continuously pipe to and from a spawned process running some other arbitrary command, as I wasn't able to find any info in the Rust documentation, tutorials or on Stack Overflow.

推荐答案

read_to_string 记录为

读取此源中 EOF 之前的所有字节

Read all bytes until EOF in this source

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

Thus, it is waiting until all the input is done, which will never happen until the shell is closed. You can fix this by reading a set amount of data from the output. Here's an example where I removed all the nice error printing you had to show the core of the solution:

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
".as_bytes()).unwrap();
        child_out.read_line(&mut line).unwrap();
        println!("{}", line);
    }
}

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

Here, we use the BufRead trait to allow reading from the input until we have read one line worth. We then print that out and continue on our loop. Of course, there's more than one line of output per line of input, so this will just have more and more waiting to be read.

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

In your real code, you will need to have to figure out when to stop reading. This could be really easy if you have fixed-size responses, or really hard if you are trying to deal with a human-interactive program.

小心直接使用 child_shell.stdinstdout,没有 Option::as_ref, Option::as_mutOption::take.直接使用 stdinstdout 会将该项目移出 Child 结构,使 Child 部分有效.例如,您将无法再对其调用 waitkill.

Be careful about using child_shell.stdin or stdout directly, without an Option::as_ref, Option::as_mut or Option::take. Using stdin or stdout directly will move that item out of the Child structure, leaving the Child partially valid. You would no longer be able to call wait or kill on it for example.

另外,您不需要调用 Error::description(&why) 之类的 trait 方法.你可以直接说why.description().

On an unrelated note, you don't need to call trait methods like Error::description(&why). You can just say why.description().

这篇关于无法多次通过管道传入或传出生成的子进程的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆