在 Rust 命令过程中写入 stdio 并从 stdout 读取

Posted

技术标签:

【中文标题】在 Rust 命令过程中写入 stdio 并从 stdout 读取【英文标题】:Writing to stdio & reading from stdout in Rust Command process 【发布时间】:2021-11-10 23:47:14 【问题描述】:

我会尽量简化我想要完成的工作,但简而言之,这是我的问题:

我正在尝试将节点 shell 作为 Rust 中的进程生成。我想传递给进程的标准输入 javascript 代码并从进程的标准输出读取 nodejs 输出。这将是一种交互式用法,其中节点 shell 被生成并不断接收 JS 指令并执行它们。

我不希望使用文件参数启动 nodejs 应用程序。

我已经阅读了很多关于 std::process::Command、tokio 以及为什么我们不能使用标准库对管道输入进行读写的内容。我一直在网上看到的一种解决方案(为了在读/写时不阻塞主线程)是使用线程来读取输出。大多数解决方案不涉及连续的写入/读取流程。

我所做的是生成 2 个线程,一个继续写入标准输入,一个继续从标准输出读取。这样,我想,我不会阻塞主线程。但是我的问题是只能主动使用 1 个线程。当我有一个标准输入线程时,标准输出甚至不接收数据。

这里是代码,cmets应该提供更多细节

pub struct Runner 
    handle: Child,
    pub input: Arc<Mutex<String>>,
    pub output: Arc<Mutex<String>>,
    input_thread: JoinHandle<()>,
    output_thread: JoinHandle<()>,


impl Runner 
    pub fn new() -> Runner 
        let mut handle = Command::new("node")
            .stdin(Stdio::piped())
            .stdout(Stdio::piped())
            .spawn()
            .expect("Failed to spawn node process!");


        // begin stdout thread part
        let mut stdout = handle.stdout.take().unwrap();
        let output = Arc::new(Mutex::new(String::new()));
        let out_clone = Arc::clone(&output);
        let output_thread = spawn(move || loop 
            // code here never executes...why ?
            let mut buf: [u8; 1] = [0];
            let mut output = out_clone.lock().unwrap();

            let what_i_read = stdout.read(&mut buf);
            println!("reading: :?", what_i_read);
            match what_i_read 
                Err(err) => 
                    println!("] Error reading from stream: ", line!(), err);
                    break;
                
                Ok(bytes_read) => 
                    if bytes_read != 0 
                        let char = String::from_utf8(buf.to_vec()).unwrap();
                        output.push_str(char.as_str());
                     else if output.len() != 0 
                        println!("result: ", output);
                        out_clone.lock().unwrap().clear();
                    
                
            
        );

        // begin stdin thread block
        let mut stdin = handle.stdin.take().unwrap();
        let input = Arc::new(Mutex::new(String::new()));
        let input_clone = Arc::clone(&input);

        let input_thread = spawn(move || loop 
            let mut in_text = input_clone.lock().unwrap();

            if in_text.len() != 0 
                println!("writing: ", in_text);
                stdin.write_all(in_text.as_bytes()).expect("!write");
                stdin.write_all("\n".as_bytes()).expect("!write");
                in_text.clear();
            
        );

        Runner 
            handle,
            input,
            output,
            input_thread,
            output_thread,
        
    
    // this function should receive commands
    pub fn execute(&mut self, str: &str) 
        let input = Arc::clone(&self.input);
        let mut input = input.lock().unwrap();

        input.push_str(str);
    

我想在主线程中使用它

let mut runner = Runner::new();
runner.execute("console.log('foo'");
println!(":?", runner.output);

我还是 Rust 的新手,但至少我已经过了借用检查器让我头撞墙的地步,我现在开始觉得它更令人愉悦了 :)

【问题讨论】:

【参考方案1】:

你是否在线程中设置了一个循环来等待输入?如何等待程序结束。

你现在,如果你这样做了

use std::thread;

fn main() 
    let _output_thread = thread::spawn(move || 
            println!("done");
    );

程序结束有时线程更快,您看不到输出“完成”。

如果你做这样的循环,是一样的

use std::thread;

fn main() 
    //let output_thread = spawn(move || loop 
            // code here never executes...why ?
    let _output_thread = thread::spawn(move || 
        //-------------------------------------^---
        loop 
            println!("done");
        
    );

如果你等待它工作得很好。

use std::thread, time;

fn main() 
    //let output_thread = spawn(move || loop 
            // code here never executes...why ?
    let _output_thread = thread::spawn(move || 
        //-------------------------------------^---
        loop 
            println!("done");
        
    );

    thread::sleep(time::Duration::from_secs(5));

使用 2 胎面的好方法就像在 https://doc.rust-lang.org/std/thread/fn.spawn.html 中解释的那样,使用通道并加入

use std::thread;
use std::sync::mpsc::channel;

let (tx, rx) = channel();

let sender = thread::spawn(move || 
    tx.send("Hello, thread".to_owned())
        .expect("Unable to send on channel");
);

let receiver = thread::spawn(move || 
    let value = rx.recv().expect("Unable to receive from channel");
    println!("", value);
);

sender.join().expect("The sender thread has panicked");
receiver.join().expect("The receiver thread has panicked");

【讨论】:

以上是关于在 Rust 命令过程中写入 stdio 并从 stdout 读取的主要内容,如果未能解决你的问题,请参考以下文章

rust cmd stdio demo

rust cmd stdio demo

rust cmd stdio demo

在 Rust 中写入子进程的标准输入?

Rust编程语言入门之项目实例:- 命令行程序

使用 node.js,我如何写入一个 MySQL 数据库并从另一个数据库读取?