std::process::Command

Command

1.基础用法

执行命令,返回结果

use std::process::Command;


fn main() {
   let mut cmd = Command::new("rustc");

    cmd.arg("--version");

    let output = cmd.output().expect("failed to execute rustc");

    println!("{:#?}", output);
}
Output {
    status: ExitStatus(
        unix_wait_status(
            0,
        ),
    ),
    stdout: "rustc 1.93.0 (254b59607 2026-01-19)\n",
    stderr: "",
}

链式调用

use std::process::Command;

fn main() {
  	// 链式调用
    let output = Command::new("rustc")
        .arg("--version")
        .output()
        .expect("failed to execute rustc");

    println!("{:#?}", output);
}

2.传参

arg和args可以混用

2.1 arg单个参数

接收单个参数(字符串 / 路径等可转为 OsStr 的类型)

⚠️避免将多个参数拼为一个字符串传递(比如 arg("-l /tmp")

use std::process::Command;

fn main() {
    let output = Command::new("ls")
        .arg("-l")
        .arg("/Code/rust_code/cmd")
        .output()
        .expect("failed to execute rustc");

    println!("{:#?}", output);
}
Output {
    status: ExitStatus(
        unix_wait_status(
            0,
        ),
    ),
    stdout: "total 16\n-rw-r--r--  1 lxd670  staff  147 Mar 10 20:36 Cargo.lock\n-rw-r--r--  1 lxd670  staff   74 Mar 10 20:33 Cargo.toml\ndrwxr-xr-x  3 lxd670  staff   96 Mar 10 21:15 src\ndrwxr-xr-x@ 5 lxd670  staff  160 Mar 10 20:36 target\n",
    stderr: "",
}

2.2 args多个参数

接收迭代器类型(数组、Vec、切片等)

use std::process::Command;

fn main() {
    let output = Command::new("ls")
        .args(["-l", "/Code/rust_code/cmd"])
        .output()
        .expect("failed to execute rustc");

    println!("{:?}", output);
}
Output {
    status: ExitStatus(
        unix_wait_status(
            0,
        ),
    ),
    stdout: "total 16\n-rw-r--r--  1 lxd670  staff  147 Mar 10 20:36 Cargo.lock\n-rw-r--r--  1 lxd670  staff   74 Mar 10 20:33 Cargo.toml\ndrwxr-xr-x  3 lxd670  staff   96 Mar 10 21:15 src\ndrwxr-xr-x@ 5 lxd670  staff  160 Mar 10 20:36 target\n",
    stderr: "",
}

2.3 arg和args混用

use std::process::Command;

fn main() {
    let output = Command::new("ls")
        .arg("-l")
        .args(["-a", "/my_space/Code/rust_code/cmd"])
        .output()
        .expect("failed to execute rustc");

    println!("{:#?}", output);
}
Output {
    status: ExitStatus(
        unix_wait_status(
            0,
        ),
    ),
    stdout: "total 24\ndrwxr-xr-x   8 lxd670  staff  256 Mar 10 20:36 .\ndrwxr-xr-x  21 lxd670  staff  672 Mar 10 20:33 ..\ndrwxr-xr-x   9 lxd670  staff  288 Mar 10 21:12 .git\n-rw-r--r--   1 lxd670  staff    8 Mar 10 20:33 .gitignore\n-rw-r--r--   1 lxd670  staff  147 Mar 10 20:36 Cargo.lock\n-rw-r--r--   1 lxd670  staff   74 Mar 10 20:33 Cargo.toml\ndrwxr-xr-x   3 lxd670  staff   96 Mar 10 21:17 src\ndrwxr-xr-x@  5 lxd670  staff  160 Mar 10 20:36 target\n",
    stderr: "",
}

3.设置目录

3.1 current_dir

等价于cd <dir> && <cmd>执行

use std::process::Command;

fn main() {
    let output = Command::new("ls")
        .current_dir("/Code/rust_code/cmd")
        .arg("-l")
        .output()
        .expect("failed to execute process");
    println!("{:#?}", output);
}
Output {
    status: ExitStatus(
        unix_wait_status(
            0,
        ),
    ),
    stdout: "total 16\n-rw-r--r--  1 lxd670  staff  147 Mar 10 20:36 Cargo.lock\n-rw-r--r--  1 lxd670  staff   74 Mar 10 20:33 Cargo.toml\ndrwxr-xr-x  3 lxd670  staff   96 Mar 10 21:28 src\ndrwxr-xr-x@ 5 lxd670  staff  160 Mar 10 20:36 target\n",
    stderr: "",
}

4.环境变量

4.1 env

添加单个环境变量

.arg("echo").arg("$TESTA")写法Rust 会把字符串$FOO"作为一个普通的文本参数传递给 echo

有值就覆盖,没值就新建

use std::process::Command;

fn main() {
    let output = Command::new("sh")
        .arg("-c")
        .arg("echo $TEST")
        .env("TEST", "test")
        .output()
        .expect("failed to execute process");
    println!("{:#?}", output);
}
Output {
    status: ExitStatus(
        unix_wait_status(
            0,
        ),
    ),
    stdout: "test\n",
    stderr: "",
}

4.1.1 传递给脚本

# a.sh
echo $AAA
use std::process::Command;

fn main() {
    let output = Command::new("./a.sh")
        .env("AAA", "ccc")
        .output()
        .expect("failed to execute process");
    println!("{:#?}", output);
}
Output {
    status: ExitStatus(
        unix_wait_status(
            0,
        ),
    ),
    stdout: "ccc\n",
    stderr: "",
}

4.2 envs

添加多个环境变量

⚠️("AAA", 1)不能传入数字类型,因为没有现实AsRef<OsStr>

可以传入HashMap向量元组

# a.sh
echo $AAA
echo $BBB
use std::process::Command;

fn main() {
    let env_args = [
        ("AAA", "1"),
        ("BBB", "2"),
    ];
    let output = Command::new("./a.sh")
        .envs(env_args)
        .output()
        .expect("failed to execute process");
    println!("{:#?}", output);
}
Output {
    status: ExitStatus(
        unix_wait_status(
            0,
        ),
    ),
    stdout: "1\n2\n",
    stderr: "",
}

4.3 env_clear

清空所有环境变量

默认情况下,子进程会继承父进程的所有环境变量。

# a.sh
echo $AAA
echo $BBB
use  std::env;
use std::process::Command;

fn main() {
    unsafe {
        env::set_var("AAA", "mainAAA");
        env::set_var("BBB", "mainBBB");
    }

    let output = Command::new("./a.sh")
        .output()
        .expect("failed to execute process");
    println!("{:#?}", output);
}
Output {
    status: ExitStatus(
        unix_wait_status(
            0,
        ),
    ),
    stdout: "mainAAA\nmainBBB\n",
    stderr: "",
}

清空所有env案列

use  std::env;
use std::process::Command;

fn main() {
    unsafe {
        env::set_var("AAA", "mainAAA");
        env::set_var("BBB", "mainBBB");
    }

    let output = Command::new("./a.sh")
        .env_clear()
        .output()
        .expect("failed to execute process");
    println!("{:#?}", output);
}
Output {
    status: ExitStatus(
        unix_wait_status(
            0,
        ),
    ),
    stdout: "\n\n",
    stderr: "",
}

4.4 env_remove

移除指定环境变量

# a.sh
echo $AAA
echo $BBB
use  std::env;
use std::process::Command;

fn main() {
    unsafe {
        env::set_var("AAA", "mainAAA");
        env::set_var("BBB", "mainBBB");
    }

    let output = Command::new("./a.sh")
        .env_remove("AAA")
        .output()
        .expect("failed to execute process");
    println!("{:#?}", output);
}
Output {
    status: ExitStatus(
        unix_wait_status(
            0,
        ),
    ),
    stdout: "\nmainBBB\n",
    stderr: "",
}

5.获取cmd内容

获取执行路径: get_current_dir() -> Option<&Path>
获取命令: get_program() -> &OsStr
获取环境变量: get_envs() -> CommandEnvs
获取执行参数: get_args() -> CommandArgs

use std::env;
use std::process::Command;

fn main() {
    let mut c = Command::new("ls");
    unsafe {
        env::set_var("RUSTA", "1");
    }
    c.arg("-l");
    c.env("AAA", "123");
    c.current_dir("/test_rust/test_trait");
    println!("{:#?}", c);

    match c.get_current_dir() {
        Some(path) => {
            // path 是 &Path,可以转换为字符串
            println!("Working directory: {}", path.display());
            // 或者转换为 OsString/String
            let path_str = path.to_string_lossy();
            println!("Path as string: {}", path_str);
        }
        None => {
            println!("Using current working directory (inherited)");
        }
    }
    println!("program: {}", c.get_program().to_string_lossy());
    println!("envs: {:?}", c.get_envs().collect::<Vec<_>>());
    println!("args: {:?}", c.get_args().collect::<Vec<_>>());
}
Command {
    program: "ls",
    args: [
        "ls",
        "-l",
    ],
    env: CommandEnv {
        clear: false,
        vars: {
            "AAA": Some(
                "123",
            ),
        },
    },
    cwd: Some(
        "/test_rust/test_trait",
    ),
}
Working directory: /test_rust/test_trait
Path as string: /test_rust/test_trait
program: ls
envs: [("AAA", Some("123"))]
args: ["-l"]

6.处理标准输入输出

特性 output() status() spawn()
阻塞行为 阻塞直到完成 阻塞直到完成 立即返回 (非阻塞)
Stdout/Stderr 捕获 (存入内存) 继承 (通常打印到终端) 可配置 (默认继承,可重定向为管道)
返回值 Output (含状态 + 数据) ExitStatus (仅状态) Child (进程句柄)
内存占用 高 (取决于输出大小) 低 (除非手动缓冲)
典型用途 获取命令结果字符串 运行安装脚本/构建工具 长期服务/交互式进程/并发

6.1 output

捕获所有输出,等待进程结束,返回输出内容

子进程打印到屏幕上的所有内容(正常输出和错误信息),不会直接显示在你当前的终端里,而是被 Rust 程序“拦截”并保存到了内存中。
.stdout.stderr字段中读取字节数据(Vec<u8>)需要转换为utf8字符串

  • 输入输出配置(固定不允许修改)
    • stdin: Null(子进程无法读取输入)
    • stdout: Piped(强制捕获到内存)
    • stderr: Piped(强制捕获到内存)

6.1.1 基础样例

use std::process::Command;

fn main() {
    let output = Command::new("ls") 
        .output() 
        .expect("Failed to execute command");

    println!("状态: {}", output.status);
    println!("_stdout_: {:?}", String::from_utf8_lossy(&output.stdout));
    println!("_stderr_: {:?}", String::from_utf8_lossy(&output.stderr)); 
}
状态: exit status: 0
_stdout_: "Cargo.lock\nCargo.toml\nsrc\ntarget\n"
_stderr_: ""

6.1.1 错误处理

6.1.1.1 expect报错

找不到命令
如果命令执行失败,expect会抛出一个异常,然后程序会panic

use std::process::Command;

fn main() {
    // 使用expect之后,会抛出异常,然后panic程序
    let output = Command::new("ls1") 
        .output() 
        .expect("Failed to execute command");

    println!("状态: {}", output.status.success());
    println!("状态: {}", output.status);
    println!("_stdout_: {:?}", String::from_utf8_lossy(&output.stdout));
    println!("_stderr_: {:?}", String::from_utf8_lossy(&output.stderr)); 
}
6.1.1.2 match捕获

命令执行成功
Err表示连l1s命令都找不到,所以不会出现status、stdout、stderr

use std::process::Command;

fn main() {
    let output = Command::new("l1s") 
        .output();

    match output {
        Ok(output) => {
            println!("状态: {}", output.status.success());
            println!("状态: {}", output.status);
            println!("_stdout_: {:?}", String::from_utf8_lossy(&output.stdout));
            println!("_stderr_: {:?}", String::from_utf8_lossy(&output.stderr)); 
        },
        Err(e) => {
            println!("{}", e);
        }
    }
}
6.1.1.3 捕获脚本错误

命令执行成功
Output 结构体中的 status、stdout 和 stderr 捕获的是子进程在运行期间产生的所有结果

# 默认echo "你好" >&1 输出到标准输出
echo "你好"

# 错误输出到标准错误
echo "错误" >&2

exit 1
use std::process::Command;

fn main() {
    let output = Command::new("./a.sh")
        .current_dir("/test_rust/test_trait/src") 
        .output();

    match output {
        Ok(output) => {
            println!("状态: {}", output.status.success());
            println!("状态: {}", output.status);
            println!("_stdout_: {:?}", String::from_utf8_lossy(&output.stdout));
            println!("_stderr_: {:?}", String::from_utf8_lossy(&output.stderr)); 
        },
        Err(e) => {
            println!("{}", e);
        }
    }
}
状态: false
状态: exit status: 1
_stdout_: "你好\n"
_stderr_: "错误\n"

6.2 status

不捕获输出(通常继承自父进程),等待进程结束,只返回退出状态码。
输出内容直接显示在终端

  • 输入输出配置(固定不允许修改)
    • stdin: Inherit(通常继承自父进程)
    • stdout: Inherit(直接打印到终端)
    • stderr: Inherit(直接打印到终端)
use std::process::Command;

fn main() {
    let s = Command::new("ls")
        .current_dir("/test_rust/test_trait/src")
        .status()
        .expect("Failed to execute command");
    // s是Result<ExitStatus> 
    println!("状态: {:?}", s);
    println!("状态: {}", s.code().unwrap());
}
main.rs
状态: ExitStatus(unix_wait_status(0))
状态: 0

6.2.1 错误处理

6.2.1.1 expect

程序直接panic了

use std::process::Command;

fn main() {
    let s = Command::new("ls1")
        .status()
        .expect("Failed to execute command");
    // s是Result<ExitStatus> 
    println!("状态: {:?}", s);
    println!("状态: {}", s.code().unwrap());
}
Failed to execute command: Os { code: 2, kind: NotFound, message: "No such file or directory" }
note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace
6.2.1.2 捕获脚本错误
use std::process::Command;

fn main() {
    let s = Command::new("./a.sh")
        .current_dir("/test_rust/test_trait/src")
        .status()
        .expect("Failed to execute command");
    // s是Result<ExitStatus> 
    println!("状态: {:?}", s);
    println!("状态: {}", s.code().unwrap());
}
你好
错误
状态: ExitStatus(unix_wait_status(256))
状态: 1

6.3 spawn

立即返回,不等待进程结束,返回一个句柄供Child你手动控制(异步执行或流式处理)

  • 输入输出配置(允许修改)
    • stdin: Inherit(通常继承自父进程)
    • stdout: Inherit(直接打印到终端)
    • stderr: Inherit(直接打印到终端)

6.3.1 child.id()

获取子进程的PID

use std::process::{Command, Stdio};

fn main() {
    let mut child = Command::new("ls")
        .stdout(Stdio::piped())
        .stderr(Stdio::piped())
        .spawn()
        .expect("Failed to start");

    println!("id: {}", child.id());
    child.wait().expect("Failed to wait on child");
}
id: 2762

6.3.2 child.wait()

阻塞当前线程:调用后,当前代码会停在这里不动,直到子进程彻底退出

回收资源:子进程结束后,操作系统回收其 PID 和资源,避免僵尸进程。

返回退出状态:返回 Result<ExitStatus>

use std::process::{Command, Stdio};
use chrono::prelude::Local;

fn main() {
    let mut child = Command::new("sleep") // 用一个长运行的命令
        .arg("10")
        .stdout(Stdio::piped())
        .spawn()
        .expect("Failed to start");

    println!("satrt: {}", Local::now());
    let res = child.wait().expect("Failed to wait on child");
    println!("res: {:?}", res);
    println!("end: {}", Local::now())
}
satrt: 2026-03-16 22:37:03.378065 +08:00
res: ExitStatus(unix_wait_status(0))
end: 2026-03-16 22:37:13.383562 +08:00

6.3.3 child.try_wait()

不阻塞:调用后立即返回,不管子进程有没有结束

检查子进程状态,返回Option<ExitStatus>

只有当它返回 Some 时,资源才被回收;如果返回 None,进程还在跑

子进程状态 try_wait() 行为 是否回收资源
未退出 立即返回 Ok(None),不阻塞、不修改子进程状态、不回收任何资源 不回收
已退出 返回 Ok(Some(ExitStatus)),同时立即回收子进程的所有系统资源(PID、文件描述符等) 回收
use chrono::prelude::Local;
use std::process::{Command, Stdio};
use std::thread;
use std::time::Duration;

fn main() {
    let mut child = Command::new("sleep") // 用一个长运行的命令
        .arg("5")
        .stdout(Stdio::piped())
        .spawn()
        .expect("Failed to start");

    println!("satrt: {}", Local::now());
    loop {
        let res = child.try_wait().expect("Failed to wait on child");
        println!("res: {:?}", res);
        match res {
            Some(status) => {
                println!("{}", status);
                break;
            }
            None => {
                thread::sleep(Duration::from_millis(1000));
            }
        }
    }
    println!("end: {}", Local::now())
}

satrt: 2026-03-16 22:40:49.542281 +08:00
res: None
res: None
res: None
res: None
res: None
res: Some(ExitStatus(unix_wait_status(0)))
exit status: 0
end: 2026-03-16 22:40:54.560609 +08:00

6.3.4 child.kill()

强制终止正在运行的子进程,kill() 只负责发送信号,它不会等待进程彻底退出,也不会自动回收资源(PID)

调用 kill() 后,必须紧接着调用 wait()try_wait() 来回收资源,否则会产生僵尸进程

use chrono::prelude::Local;
use std::process::{Command, Stdio};
use std::thread;
use std::time::Duration;

fn main() {
    let mut child = Command::new("sleep") // 睡眠30秒
        .arg("30")
        .stdout(Stdio::piped())
        .spawn()
        .expect("Failed to start");

    println!("satrt: {}", Local::now());
    println!("PID: {}", child.id());
    child.kill().expect("Failed to kill child");
    // 睡眠10秒
    thread::sleep(Duration::from_millis(10000));
    println!("PID: {}", child.id());
    println!("end: {}", Local::now());

    let res = child.wait().expect("Failed to wait on child");
    println!("res: {}", res);
}
satrt: 2026-03-16 22:46:32.346687 +08:00
PID: 5660
PID: 5660
end: 2026-03-16 22:46:42.351885 +08:00
res: signal: 9 (SIGKILL)
ps -d 5543 
  PID TTY           TIME CMD
 5543 ttys000    0:00.00 <defunct>
# 10秒之后执行
ps -d 5543
  PID TTY           TIME CMD

6.3.5 child.wait_with_output()

wait_with_output内部自动调用了wait资源回收

  • 等待进程结束(相当于自动调用了 wait)。
  • 捕获所有标准输出到内存中。
  • 捕获所有标准错误到内存中。
use std::process::{Command, Stdio};

fn main() {
    let mut child = Command::new("ls")
        .stdout(Stdio::piped())
        .stderr(Stdio::piped())
        .spawn()
        .expect("Failed to start");

    let s = child.wait_with_output().expect("err");
    println!("{:#?}", s);
}
Output {
    status: ExitStatus(
        unix_wait_status(
            0,
        ),
    ),
    stdout: "Cargo.lock\nCargo.toml\na.sh\nsrc\ntarget\n",
    stderr: "",
}
6.3.5.1 wait_with_output比较stdout/stderr
use std::process::{Command, Stdio};
use std::io::Read;

fn main() {
    let mut child = Command::new("echo")
        .arg("Hello")
        .stdout(Stdio::piped()) // 必须手动设置管道
        .stderr(Stdio::piped())
        .spawn()
        .expect("Failed");

    // 1. 手动获取 stdout 句柄
    let mut stdout = child.stdout.take().expect("No stdout");
    let mut stderr = child.stderr.take().expect("No stderr");

    // 2. 等待进程结束
    let status = child.wait().expect("Failed");

    // 3. 手动读取所有内容 (还需要处理 BufReader 避免死锁)
    let mut out_buf = Vec::new();
    let mut err_buf = Vec::new();
    stdout.read_to_end(&mut out_buf).unwrap();
    stderr.read_to_end(&mut err_buf).unwrap();

    println!("Status: {:?}", status);
    println!("Output: {}", String::from_utf8_lossy(&out_buf));
}
Status: ExitStatus(unix_wait_status(0))
Output: Cargo.lock
Cargo.toml
a.sh
src
target

6.3.6 输入stdint

6.3.6.1 stdint + Stdio::inherit

终端会一直等待输入,程序不会自动结束

  • stdin(Stdio::inherit()), 将标准输入接到终端
  • 命令(这里是grep)就会等待终端的输入
  • grep是一个流处理工具,它会一直读取直到遇到 EOF(文件结束符)。
    只要没收到EOF,它就认为"用户还没输完,我接着等"
  • 发送EOF:
    • 自动发送EOF
      • 读取文件,文件读完了系统自动发 EOF, 读取终端(键盘)
    • 必须用户手动发送EOF
      • Linux / macOS:按 Ctrl + D
      • Windows:按 Ctrl + Z
use std::process::{Command, Stdio};

fn main() {
    let mut child = Command::new("grep")
        .arg("a")
        .stdin(Stdio::inherit())
        .stdout(Stdio::piped())
        .spawn()
        .expect("grep failed to start");

    let s = child.wait_with_output();
    println!("{:?}", s);
}
aa
zz
az
# 需要按ctrl + d 结束输入
Ok(Output { status: ExitStatus(unix_wait_status(0)), stdout: "aa\naz\n", stderr: "" })
6.3.6.2 stdint + Stdio::piped

必须关闭stdin,否者会卡主

  • 关闭方式
    • 作用域关闭{ let stdin = ...; }
    • 显示drop(stdin)
    • let绑定 let _ = stdin;
// Write必须引入这个trait才能用stdin.write_all
use std::{io::Write, process::{Command, Stdio}};

fn main() {
    let mut child = Command::new("grep")
        .arg("test")
        .stdin(Stdio::piped())
        .stdout(Stdio::piped())
        .spawn()
        .expect("grep failed to start");
    
    // 获取stdin + stdin写入 + stdin关闭(自动发送EOF)
    {
        let mut stdin = child.stdin.take().expect("Failed to get stdin");
        // stdin输入内容
        stdin.write_all(b"hello world\ntest abc\n").expect("write failed");
    }


    let s = child.wait_with_output();
    println!("{:?}", s);
}
Ok(Output { status: ExitStatus(unix_wait_status(0)), stdout: "test abc\n", stderr: "" })
6.3.6.3 stdint + Stdio::from

效果cat < test.log

123
456
789
use std::process::{Command, Stdio};
use std::fs::File;
use std::path::Path;

fn main() {
    let  file = File::open(Path::new("test.log")).unwrap();

    let mut child = Command::new("cat")
        .stdin(Stdio::from(file))
        .stdout(Stdio::piped())
        .spawn()
        .expect("grep failed to start");

    let s = child.wait_with_output().expect("failed to wait on child");
    println!("{:#?}", s);
}
Output {
    status: ExitStatus(
        unix_wait_status(
            0,
        ),
    ),
    stdout: "123\n456\n789",
    stderr: "",
}

6.3.7 输出stdout/stderr

6.3.7.1 stdout + Stdio::inherit

输出到终端(继承主程序)

use std::process::{Command, Stdio};

fn main() {
    let mut child = Command::new("ls")
        .stdin(Stdio::inherit())
        .stdout(Stdio::inherit())
        .spawn()
        .expect("grep failed to start");

    let s = child.wait_with_output();
    println!("{:?}", s);
}
Cargo.lock      Cargo.toml      a.sh            src             target
Output {
    status: ExitStatus(
        unix_wait_status(
            0,
        ),
    ),
    stdout: "",
    stderr: "",
}
6.3.7.2 stdout + Stdio::piped

输出内容被rust捕获


use std::process::{Command, Stdio};

fn main() {
    let mut child = Command::new("ls")
        .stdin(Stdio::inherit())
        .stdout(Stdio::piped())
        .spawn()
        .expect("grep failed to start");

    let s = child.wait_with_output().expect("failed to wait on child");
    println!("{:#?}", s);
}
Output {
    status: ExitStatus(
        unix_wait_status(
            0,
        ),
    ),
    stdout: "Cargo.lock\nCargo.toml\na.sh\nsrc\ntarget\n",
    stderr: "",
}
6.3.7.3 stdout + Stdio::null

输出到/dev/null中

use std::process::{Command, Stdio};

fn main() {
    let mut child = Command::new("ls")
        .stdin(Stdio::inherit())
        .stdout(Stdio::null())
        .spawn()
        .expect("grep failed to start");

    let s = child.wait_with_output().expect("failed to wait on child");
    println!("{:#?}", s);
}
Output {
    status: ExitStatus(
        unix_wait_status(
            0,
        ),
    ),
    stdout: "",
    stderr: "",
}
6.3.7.4 stdout + Stdio::from

输出到文件只中

use std::process::{Command, Stdio};
use std::fs::File;
use std::io::Write;

fn main() {

    let file = File::create("output.log").expect("无法创建文件");

    let mut child = Command::new("ls")
        .stdin(Stdio::inherit())
        .stdout(Stdio::from(file))
        .spawn()
        .expect("grep failed to start");

    let s = child.wait_with_output().expect("failed to wait on child");
    println!("{:#?}", s);
}
Output {
    status: ExitStatus(
        unix_wait_status(
            0,
        ),
    ),
    stdout: "",
    stderr: "",
}

7.Stdio说明

  • Stdio::inherit(): 继承父进程的流
  • Stdio::piped(): 创建管道,类似linux的|
  • Stdio::null(): 连接到空设备。类似linux的/dev/null
  • Stdio::from(file): 输入/输出到文件中

7.1 Stdio::inherit

输出内容直接显示在终端(脚本就是在终端执行的)

use std::process::{Command, Stdio};

fn main() {
    let mut child = Command::new("ls")
        .current_dir("./src")
        .stdout(Stdio::inherit())
        .stderr(Stdio::inherit())
        .spawn()
        .expect("Failed to start");
}
cargo run
a.sh    main.rs

7.1.1 案例

// 情况 1:父进程在终端运行
// 子进程的输出 → 父进程的 stdout → 终端
Command::new("ls").stdout(Stdio::inherit()).spawn();

// 情况 2:父进程的 stdout 被重定向到文件
// 运行:cargo run > output.txt
// 子进程的输出 → 父进程的 stdout → output.txt 文件
Command::new("ls").stdout(Stdio::inherit()).spawn();

// 情况 3:父进程在管道中
// 运行:cargo run | grep xxx
// 子进程的输出 → 父进程的 stdout → 管道 → grep
Command::new("ls").stdout(Stdio::inherit()).spawn();

7.2 Stdio::piped()

输出的内容被rust捕获,不会输出到终端

use std::process::{Command, Stdio};

fn main() {
    let mut child = Command::new("ls")
        .stdout(Stdio::piped())
        .stderr(Stdio::piped())
        .spawn()
        .expect("Failed to start");
}

7.3 Stdio::null()

Command::new("ls")
    .stdout(Stdio::null())  // 扔掉标准输出
    .stderr(Stdio::null())  // 扔掉错误输出
    .output()
    .expect("failed");
// 屏幕上干净如初

7.4 Stdio::from(File)

创建一个文件句柄,并指定文件路径

use std::fs::File;

let log_file = File::create("log.txt").unwrap();

Command::new("ls")
    // 把 stdout 重定向到 log.txt 文件
    .stdout(Stdio::from(log_file)) 
    .spawn()
    .unwrap();

8.案例

非阻塞启动与等待

use std::process::Command;
use std::thread;
use std::time::Duration;

fn main() {
    println!("1. 启动耗时任务 (sleep 3秒)...");
    
    // spawn 立即返回,不会等待 sleep 结束
    let mut child = Command::new("sleep")
        .arg("3")
        .spawn()
        .expect("Failed to start sleep command");

    println!("2. 任务已启动,主程序继续工作...");
    
    // 模拟主程序在做其他事情
    thread::sleep(Duration::from_secs(1));
    println!("   (主程序正在处理其他逻辑...)");
    
    thread::sleep(Duration::from_secs(1));
    println!("   (主程序还在忙...)");

    println!("3. 现在等待子进程结束并获取结果...");
    
    // 手动等待进程结束并捕获输出
    let output = child.wait_with_output()
        .expect("Failed to wait on child");

    println!("4. 任务完成!状态: {}", output.status);
    if !output.stdout.is_empty() {
        println!("输出: {}", String::from_utf8_lossy(&output.stdout));
    }
}

向子进程写入数据

use std::process::{Command, Stdio};
use std::io::Write;

fn main() {
    println!("启动 cat 命令(它会回显你输入的内容)...");

    let mut child = Command::new("cat")
        .stdin(Stdio::piped())  // 关键:允许写入 stdin
        .stdout(Stdio::piped()) // 关键:允许读取 stdout
        .stderr(Stdio::inherit()) // 让错误信息直接打印到终端
        .spawn()
        .expect("Failed to spawn cat");

    {
        // 获取 stdin 的句柄
        let mut stdin = child.stdin.take()
            .expect("Failed to open stdin");

        println!("正在向 cat 写入数据...");
        // 写入数据
        stdin.write_all(b"Hello from Rust!\n").expect("Failed to write");
        stdin.write_all(b"Second line.\n").expect("Failed to write");
        
        // 【至关重要】
        // 必须显式 drop 掉 stdin,或者让它离开作用域。
        // 这会给子进程发送 EOF (End Of File)。
        // 如果不这样做,cat 会一直等待更多输入,永远不会结束,导致下面的 wait_with_output 卡死。
        drop(stdin); 
        println!("输入已关闭 (发送 EOF)。");
    }

    // 现在可以安全地等待并获取回显
    let output = child.wait_with_output()
        .expect("Failed to wait");

    println!("\n--- 捕获到的 stdout ---");
    println!("{}", String::from_utf8_lossy(&output.stdout));
    println!("-----------------------");
}

实时读取海量输出

use std::process::{Command, Stdio};
use std::io::{BufRead, BufReader};
use std::thread;

fn main() {
    println!("启动 ping 命令 (实时读取输出)...");

    let mut child = Command::new("ping")
        .arg("-c")
        .arg("5") // Linux: ping 5次; Windows 请用 "ping" "-n" "5" "google.com"
        .arg("google.com")
        .stdout(Stdio::piped()) // 必须 piped 才能读取
        .stderr(Stdio::inherit())
        .spawn()
        .expect("Failed to spawn ping");

    // 获取 stdout 并包装成 BufReader 以便逐行读取
    let stdout = child.stdout.take()
        .expect("Failed to open stdout");
    
    let reader = BufReader::new(stdout);

    // 在新线程中读取,防止阻塞主线程(可选,视需求而定)
    // 这里直接在主线程演示
    for line in reader.lines() {
        match line {
            Ok(l) => {
                // 实时处理每一行
                println!("[实时日志] {}", l);
                
                // 你可以在这里加逻辑,比如发现 "unreachable" 就报警
                if l.contains("unreachable") {
                    println!("⚠️ 检测到网络问题!");
                }
            },
            Err(e) => eprintln!("读取行失败: {}", e),
        }
    }

    // 等待进程彻底结束
    let status = child.wait().expect("Failed to wait");
    println!("\n进程结束,退出码: {}", status);
}

超时控制与强制杀死进程

use std::process::{Command, Stdio};
use std::time::{Duration, Instant};
use std::thread;

fn main() {
    println!("启动一个可能会卡死的命令 (sleep 10)...");
    
    let mut child = Command::new("sleep")
        .arg("10") // 故意设长一点
        .spawn()
        .expect("Failed to spawn");

    let timeout = Duration::from_secs(3); // 设定超时时间为 3 秒
    let start_time = Instant::now();

    println!("等待进程 (超时限制: {:?})...", timeout);

    // 简单的轮询检查机制
    loop {
        // 检查是否已经超时
        if start_time.elapsed() > timeout {
            println!("⏰ 超时!正在杀死进程...");
            child.kill().expect("Failed to kill process");
            break;
        }

        // 检查进程是否已经结束
        // try_wait() 是非阻塞的:如果没结束返回 Ok(None),结束了返回 Ok(Some(status))
        match child.try_wait() {
            Ok(Some(status)) => {
                println!("进程正常结束,状态: {}", status);
                return; // 退出
            },
            Ok(None) => {
                // 进程还在跑,睡一小会儿再检查
                thread::sleep(Duration::from_millis(500));
            },
            Err(e) => {
                eprintln!("检查进程状态出错: {}", e);
                break;
            }
        }
    }

    // 如果是因为超时被杀死的,我们需要再次 wait 来清理资源 (避免僵尸进程)
    match child.wait() {
        Ok(status) => println!("进程已被杀死,最终状态: {}", status),
        Err(e) => eprintln!("等待被杀死的进程时出错: {}", e),
    }
}

死锁

操作系统为每个进程的管道(Pipe)提供了一个固定大小的内核缓冲区

读写都需要使用缓冲区,Stdio::piped()需要重新写入缓冲区被rust程序读取

只要将 .stdout() 设置为 inheritnullfrom(file)死锁现象就会彻底消失

// 错误示范:顺序执行,必死锁
// 前提my_program命令会输出大量信息到stdout/stderr
// 并且需要捕获被rust使用Stdio::piped()
let mut child = Command::new("my_program")
    .stdin(Stdio::piped())
    .stdout(Stdio::piped())
    .spawn()?;

{
    let mut stdin = child.stdin.take().unwrap();
    // 1. 主进程试图写入大量数据 (例如 1MB)
    // 如果子进程因为 stdout 满了而卡住,它就不会读 stdin。
    // 如果 stdin 的管道缓冲区也满了,这里就会卡住!
    stdin.write_all(large_data)?; 
}

// 2. 主进程试图读取 stdout
// 但子进程早就卡在第一步(写 stdout)了,根本没机会读到 stdin 并继续运行产生更多输出或退出
let output = child.wait_with_output()?; 

解决方法

  • 使用多线程 : 开启一个线程专门负责(stdout/stderr),主线程负责(stdin)
  • 使用第三方库:
    • duct (同步)
    • tokio / async-std (异步场景)
posted @ 2026-03-16 23:33  lxd670  阅读(32)  评论(0)    收藏  举报