rust-subprocess icon indicating copy to clipboard operation
rust-subprocess copied to clipboard

Question on struck when interacting with subprocess

Open dbsxdbsx opened this issue 2 years ago • 2 comments

I have a shell app. Generally, I would open it in a shell, then I input a command "ucci" to it, then it feedbacks some lines of strings. And I would still interact it with other commands until I input "quit", then it would quit.

I wrote code like this:

use subprocess::{Popen, PopenConfig, Redirection};

fn main() -> io::Result<()> {
    let cmd = "./myShellApp.exe";

    // test crate
    let mut p = Popen::create(
        &[cmd],
        PopenConfig {
            stdin: Redirection::Pipe,
            stdout: Redirection::Pipe,
            ..Default::default()
        },
    )
    .expect("open app failed");

    let (out, err) = p.communicate(Some("ucci\n"))?;
    match out {
        Some(s) => println!("{}", s),
        None => todo!(),
    }
    match err{
        Some(e) => println!("{}", e),
        None => todo!(),
    }

But the code just stuck at let (out, err) = p.communicate(Some("ucci\n"))?;. It seems that it would not quit until the sub process in terminated. How should I fix it?

dbsxdbsx avatar Jun 13 '22 10:06 dbsxdbsx

communicate() is not the right tool for the job because it reads all output from the subprocess, i.e. it expects an end-of-file.

If the "some lines of strings" are self-delimited (i.e. you are able to detect when they are done), then you could simply read from p.stdout yourself. If you don't know how many lines will be printed, and you can't detect the last one, then you cannot solve this in a general way.

hniksic avatar Jun 13 '22 10:06 hniksic

communicate() is not the right tool for the job because it reads all output from the subprocess, i.e. it expects an end-of-file.

If the "some lines of strings" are self-delimited (i.e. you are able to detect when they are done), then you could simply read from p.stdout yourself. If you don't know how many lines will be printed, and you can't detect the last one, then you cannot solve this in a general way.

Actually, I would like to listen to the output of this app within a time limit after sending input to it. In dart, I could do it like this:

Future<Process?> init() {
    ready = false;
    if (!isSupportEngine) {
      return Future.value(null);
    }
    // path to the process app
    String path = Directory.current.path + '/assets/engines/$engine';
    if (!File(path).existsSync()) {
      path = Directory.current.path +
          '/data/flutter_assets/assets/engines/$engine';
    }
    return Process.start(path, [], mode: ProcessStartMode.normal).then((value) {
      process = value;
      ready = true;
      process?.stdout.listen(onMessage);
      process?.stdin.writeln('ucci');
      return process!;
    });
  }


void onMessage(List<int> event) {
    String lines = String.fromCharCodes(event).trim();
    lines.split('\n').forEach((line) {
      line = line.trim();
      if (line == 'bye') {
        ready = false;
        process = null;
      } else if (line.isNotEmpty && this.hasListeners) {
        if (line.startsWith('nobestmove') || line.startsWith('bestmove ')) {
          ...
          this.notifyListeners(line);
        }
      }
    });
  }

But I don't know how to do it in rust.
And even in the simple version, I modified the code like this:

 let cmd = "./myShellApp.exe";
    let mut p = Popen::create(
        &[cmd],
        PopenConfig {
            stdin: Redirection::Pipe,
            stdout: Redirection::Pipe,
            ..Default::default()
        },
    )
    .expect("open app failed");

    p.stdin
        .as_mut()
        .unwrap()
        .write_all(b"ucci\n")?;

    if let Some(status) = p.wait_timeout(Duration::new(2, 0)).unwrap() {
        println!("process finished as {:?}", status);
        let x = &p.stdout;
    } else {
        p.kill().unwrap();
        p.wait().unwrap();
        println!("process killed");
    }

It still killed the process. What is wrong?

dbsxdbsx avatar Jun 13 '22 13:06 dbsxdbsx