如何终止Rust中通过script启动的运行中命令?
问题:Ctrl+C无法终止Rust代码中执行的命令
你编写的Rust代码可以运行指定命令、实时输出内容并处理执行结果,但执行sleep 10 && echo test这类命令时,按下Ctrl+C无法终止进程,而直接在终端运行该命令却能正常终止。
原因分析
- 代码中使用
script命令包裹目标命令,script默认会创建伪终端(PTY),且默认忽略SIGINT信号(Ctrl+C触发的终止信号),导致目标进程无法收到终止指令。 - Rust主进程在
child.wait()处阻塞,未处理SIGINT信号,即便用户按下Ctrl+C,主进程也不会主动终止子进程。
解决方案
需做两处关键改动:
- 给
script命令添加参数,让它转发信号给子进程并返回真实退出码; - 在Rust代码中监听SIGINT信号,收到信号时主动终止子进程。
修改后的完整代码:
use std::{ io::{BufRead, BufReader, Write}, process::{Command, Stdio, Child}, sync::{Arc, Mutex}, signal::unix::{signal, SignalKind}, }; pub struct CmdRunner { pub command: std::process::Command, } impl CmdRunner { pub fn new(command_string: &str, input: Option<&str>) -> CmdRunner { let mut command = Command::new("script"); // --return让script返回子进程的退出码,-e让它转发信号给子进程 command.args(["--return", "-qec"]); if let Some(input_str) = input { command.arg(command_string.replace("{}", input_str)); } else { command.arg(command_string); } command.arg("/dev/null"); CmdRunner { command } } pub fn run(&mut self, stdout: &mut impl Write) { self.command.stdout(Stdio::piped()); self.command.stderr(Stdio::piped()); let mut child = self.command.spawn().expect("failed to spawn command"); let child_arc = Arc::new(Mutex::new(child)); // 监听SIGINT信号,收到后终止子进程 let child_clone = Arc::clone(&child_arc); let _signal_handler = signal(SignalKind::interrupt()).expect("failed to set signal handler"); std::thread::spawn(move || { let _ = _signal_handler.recv(); if let Ok(mut child) = child_clone.lock() { let _ = child.kill(); } }); let stdout_pipe = child_arc.lock().unwrap().stdout.take().unwrap(); let stdout_reader = BufReader::new(stdout_pipe); let stderr_pipe = child_arc.lock().unwrap().stderr.take().unwrap(); let stderr_reader = BufReader::new(stderr_pipe); let stdout_thread = std::thread::spawn(move || { for line in stdout_reader.lines() { if let Ok(line) = line { print!("{}\r\n", line); } } }); let stderr_thread = std::thread::spawn(move || { for line in stderr_reader.lines() { if let Ok(line) = line { print!("{}\r\n", line); } } }); let status = { let mut child = child_arc.lock().unwrap(); child.wait().expect("failed to wait for command") }; stdout_thread.join().expect("failed to join stdout thread"); stderr_thread.join().expect("failed to join stderr thread"); if status.success() { write!(stdout, "Command executed successfully\r\n").unwrap(); } else { write!( stdout, "Command failed with exit code {}\r\n", status.code().unwrap_or(-1) ) .unwrap(); } } } fn main() { let mut cmd_runner = CmdRunner::new("sleep 10 && echo test", None); let mut stdout = Vec::new(); cmd_runner.run(&mut stdout); println!("stdout: {:?}", String::from_utf8_lossy(&stdout).to_string()); }
关键改动说明
script参数调整:添加--return和-e,-e让script将收到的信号转发给子进程,--return让它返回子进程的真实退出码,而非自身的退出码。- SIGINT信号监听:通过
signal函数监听SIGINT信号,收到信号后利用Arc<Mutex<Child>>安全获取子进程实例,调用kill()终止子进程,主进程会随之结束等待并处理后续逻辑。
内容的提问来源于stack exchange,提问作者wyc
相关产品推荐
相关产品推荐

