Welcome to OGeek Q&A Community for programmer and developer-Open, Learning and Share
Welcome To Ask or Share your Answers For Others

Categories

0 votes
655 views
in Technique[技术] by (71.8m points)

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

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.

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),
        }
    }
}

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())

to this:

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

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!)

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.

See Question&Answers more detail:os

与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
Welcome To Ask or Share your Answers For Others

1 Reply

0 votes
by (71.8m points)

read_to_string is documented as

Read all bytes until EOF in this source

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);
    }
}

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.

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.

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


与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
OGeek|极客中国-欢迎来到极客的世界,一个免费开放的程序员编程交流平台!开放,进步,分享!让技术改变生活,让极客改变未来! Welcome to OGeek Q&A Community for programmer and developer-Open, Learning and Share
Click Here to Ask a Question

...