Files
DevFlow/crates/df-execute/src/shell.rs

114 lines
3.5 KiB
Rust
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
//! Shell 执行器 — 通过 tokio::process 执行 shell 命令
use serde::{Deserialize, Serialize};
use std::process::Stdio;
/// Shell 命令执行结果
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ShellResult {
/// 标准输出
pub stdout: String,
/// 标准错误
pub stderr: String,
/// 退出码
pub exit_code: Option<i32>,
/// 执行耗时(毫秒)
pub duration_ms: u64,
}
/// Shell 类型选择
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
#[serde(rename_all = "snake_case")]
pub enum ShellType {
/// Windows cmd.exe (默认 Windows)
Cmd,
/// PowerShell
PowerShell,
/// Unix sh (默认非 Windows)
Sh,
}
impl Default for ShellType {
fn default() -> Self {
if cfg!(windows) { ShellType::Cmd } else { ShellType::Sh }
}
}
/// Shell 命令执行请求
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ShellRequest {
/// 要执行的命令
pub command: String,
/// 工作目录
pub working_dir: Option<String>,
/// 环境变量
pub env: std::collections::HashMap<String, String>,
/// 超时时间None 表示不超时
pub timeout_secs: Option<u64>,
/// Shell 类型默认Windows→Cmd, 非Windows→Sh
#[serde(default)]
pub shell_type: Option<ShellType>,
}
/// 执行 Shell 命令
///
/// TODO: 完整实现,支持超时、环境变量、工作目录等
pub async fn execute(request: ShellRequest) -> anyhow::Result<ShellResult> {
let start = std::time::Instant::now();
let shell_type = request.shell_type.unwrap_or_default();
let mut cmd = match shell_type {
ShellType::PowerShell => {
let mut c = tokio::process::Command::new("powershell");
c.arg("-NoProfile").arg("-Command").arg(&request.command);
c.stdout(Stdio::piped()).stderr(Stdio::piped());
c
}
ShellType::Cmd => {
let mut c = tokio::process::Command::new("cmd");
c.arg("/C").arg(&request.command);
c.stdout(Stdio::piped()).stderr(Stdio::piped());
c
}
ShellType::Sh => {
let mut c = tokio::process::Command::new("sh");
c.arg("-c").arg(&request.command);
c.stdout(Stdio::piped()).stderr(Stdio::piped());
c
}
};
// CR-15-1: kill_on_drop(true) 让 Command 被 drop 时主动 kill 子进程。
// 配合 tokio::time::timeout 超时场景:超时 drop future → Command 析构 → kill 子进程,
// 不再让超时后的命令变孤儿继续后台跑(长 hang 命令/死循环仍占资源)。
// 对齐 tool_registry.rs:514「进程已终止」文案名副其实。tokio 1.52.3 支持。
cmd.kill_on_drop(true);
if let Some(dir) = &request.working_dir {
cmd.current_dir(dir);
}
for (key, value) in &request.env {
cmd.env(key, value);
}
let output = match request.timeout_secs {
Some(secs) => tokio::time::timeout(
std::time::Duration::from_secs(secs),
cmd.output(),
)
.await
.map_err(|_| anyhow::anyhow!("命令执行超时: {}秒", secs))??,
None => cmd.output().await?,
};
let duration = start.elapsed().as_millis() as u64;
Ok(ShellResult {
stdout: String::from_utf8_lossy(&output.stdout).to_string(),
stderr: String::from_utf8_lossy(&output.stderr).to_string(),
exit_code: output.status.code(),
duration_ms: duration,
})
}