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

如何终止Rust中通过script启动的运行中命令?

问题:Ctrl+C无法终止Rust代码中执行的命令

你编写的Rust代码可以运行指定命令、实时输出内容并处理执行结果,但执行sleep 10 && echo test这类命令时,按下Ctrl+C无法终止进程,而直接在终端运行该命令却能正常终止。

原因分析

  • 代码中使用script命令包裹目标命令,script默认会创建伪终端(PTY),且默认忽略SIGINT信号(Ctrl+C触发的终止信号),导致目标进程无法收到终止指令。
  • Rust主进程在child.wait()处阻塞,未处理SIGINT信号,即便用户按下Ctrl+C,主进程也不会主动终止子进程。

解决方案

需做两处关键改动:

  1. 给script命令添加参数,让它转发信号给子进程并返回真实退出码;
  2. 在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());
}

关键改动说明

  1. script参数调整:添加--return和-e,-e让script将收到的信号转发给子进程,--return让它返回子进程的真实退出码,而非自身的退出码。
  2. SIGINT信号监听:通过signal函数监听SIGINT信号,收到信号后利用Arc<Mutex<Child>>安全获取子进程实例,调用kill()终止子进程,主进程会随之结束等待并处理后续逻辑。

内容的提问来源于stack exchange,提问作者wyc

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.21 22:05:08