You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何在Rust中实现可被事件中断的stdin命令行输入提示?

问题描述

我正在开发一款命令行终端/解析器,它会显示提示并接收用户命令,目前通过以下Rust代码实现:

'prompt: loop{
    // 显示提示、读取输入、执行命令的逻辑...
}

我希望实现这样的功能:当有触发器(如定时器等)触发时,输出文本以“中断”当前的输入提示,之后立即恢复接收stdin输入。效果示例如下:

[11:39:00] Enter your command:
Alert! AAPL has decreased by greater than 5%
[11:40:00] Enter your command:

核心需求是循环需持续运行并检查事件,同时接收用户输入。请问实现该功能的最佳方式是什么?


解决方案

在Rust中实现该功能的核心是避免阻塞式输入读取,同时处理终端输出与输入的同步问题,以下是两种实用方案:

方案一:异步IO(Tokio 生态)

异步IO可以让你同时监听用户输入和触发器事件,无需额外线程调度,适合大多数场景。

  1. 添加依赖到Cargo.toml:
[dependencies]
tokio = { version = "1.0", features = ["full"] }
chrono = "0.4"
  1. 实现代码示例:
use tokio::io::{AsyncBufReadExt, AsyncWriteExt, BufReader};
use tokio::time::{interval, Duration};
use chrono::Local;

#[tokio::main]
async fn main() {
    let stdin = BufReader::new(tokio::io::stdin());
    let mut lines = stdin.lines();
    // 模拟1分钟一次的触发器事件
    let mut trigger_interval = interval(Duration::from_secs(60));

    // 初始显示提示
    print_prompt().await;

    loop {
        tokio::select! {
            // 处理用户输入
            input_result = lines.next_line() => {
                match input_result {
                    Ok(Some(cmd)) => {
                        println!("执行命令:{}", cmd);
                        print_prompt().await;
                    }
                    Ok(None) => break, // stdin 关闭
                    Err(e) => eprintln!("读取输入失败:{}", e),
                }
            }
            // 处理触发器事件
            _ = trigger_interval.tick() => {
                // 清除当前输入行(避免和用户输入重叠)
                print!("\r\x1B[K");
                println!("Alert! AAPL has decreased by greater than 5%");
                print_prompt().await;
            }
        }
    }
}

async fn print_prompt() {
    let timestamp = Local::now().format("%H:%M:%S");
    print!("[{}] Enter your command: ", timestamp);
    tokio::io::stdout().flush().await.unwrap();
}

这里用tokio::select!同时等待输入和定时器事件,\r\x1B[K是ANSI转义序列,用于清除当前行,确保触发器输出和新提示不会与用户未完成的输入混在一起。

方案二:线程 + 终端原始模式

如果不想依赖异步生态,可以用主线程处理输入,单独线程管理触发器,同时借助终端原始模式避免阻塞式读取的问题。

  1. 添加依赖到Cargo.toml:
[dependencies]
termion = "1.5"
chrono = "0.4"
  1. 实现代码示例:
use chrono::Local;
use std::io::{self, Write};
use std::sync::mpsc;
use std::thread;
use std::time::Duration;
use termion::input::TermRead;
use termion::raw::IntoRawMode;

fn main() {
    let (trigger_tx, trigger_rx) = mpsc::channel();

    // 触发器线程:模拟定时事件
    thread::spawn(move || {
        loop {
            thread::sleep(Duration::from_secs(60));
            let _ = trigger_tx.send(());
        }
    });

    // 切换到终端原始模式:禁用行缓冲,支持实时输入处理
    let mut stdout = io::stdout().into_raw_mode().unwrap();
    let stdin = io::stdin();

    loop {
        // 显示输入提示
        print_prompt(&mut stdout);

        // 先检查是否有触发器事件
        if trigger_rx.try_recv().is_ok() {
            handle_trigger_event(&mut stdout);
            continue;
        }

        // 逐字符读取输入(原始模式下无行缓冲)
        for key in stdin.keys() {
            let key = key.unwrap();
            match key {
                termion::event::Key::Char('\n') => {
                    writeln!(stdout, "\n执行命令").unwrap();
                    stdout.flush().unwrap();
                    break;
                }
                termion::event::Key::Char(c) => {
                    // 回显用户输入的字符
                    write!(stdout, "{}", c).unwrap();
                    stdout.flush().unwrap();
                }
                _ => {}
            }

            // 每读取一个字符就检查一次触发器事件
            if trigger_rx.try_recv().is_ok() {
                handle_trigger_event(&mut stdout);
                break;
            }
        }
    }
}

fn print_prompt(stdout: &mut impl Write) {
    let timestamp = Local::now().format("%H:%M:%S");
    write!(stdout, "[{}] Enter your command: ", timestamp).unwrap();
    stdout.flush().unwrap();
}

fn handle_trigger_event(stdout: &mut impl Write) {
    // 清除当前行并输出告警
    write!(stdout, "\r\x1B[KAlert! AAPL has decreased by greater than 5%\n").unwrap();
    stdout.flush().unwrap();
}

该方案通过termion切换到终端原始模式,避免了read_line的阻塞问题,同时用线程间通信传递触发器事件,主线程在输入间隙检查事件并处理。


内容的提问来源于stack exchange,提问作者Nick S.

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.10 10:20:32