进程通信

一、核心概念速查

Command 的三种「执行」方法

方法 返回类型 何时用 典型场景
.output() Result<Output, io::Error> 只关心最终结果,一次性跑完拿输出 run_command
.spawn() Result<Child, io::Error> 需要边跑边交互(双向通信) pipe_through_*
.status() Result<ExitStatus, io::Error> 只关心退出码,不关心输出 get_exit_code

Command 的配置方法

方法 作用
Command::new(program) 指定要启动的程序
.arg(x) 加一个参数
.args(iter) 加一堆参数(接受数组/切片/Vec)
.stdinpiped() 接管子进程输入,接管道
.stdoutpiped() 接管子进程输出,接管道

错误处理三件套

写法 成功 失败 适用条件
.unwrap() 取出值 panic 崩溃 函数不返回 Result
.expect("msg") 取出值 panic + 自定义信息 函数不返回 Result
? 取出值 提前 return Err 函数必须返回 Result

处理 Option 的两种方式

写法 Some(x) None
.unwrap() 取出 x panic
.unwrap_or(默认值) 取出 x 返回默认值

二、两种「模式」

模式 A:执行并拿结果(单向)

Command::new(程序) → .args(参数) → .output()/.status() → 处理结果

用于:run_command、get_exit_code、run_command_with_result

模式 B:双向管道通信

spawn → take stdin → write_all → drop(stdin) → take stdout → read_to_string → wait

用于:pipe_through_cat、pipe_through_grep


三、5 个函数对照表

函数 模式 关键方法 新知识点
run_command A .output() String::from_utf8 + unwrap
pipe_through_cat B .spawn() + .take() + drop 双向管道全流程
get_exit_code A .status() + .code() sh -c 技巧、unwrap_or
run_command_with_result A .output()? ? 运算符、map_err
pipe_through_grep B 同 cat,多 .arg(pattern) 模式 B 复用

四、完整代码合集(可直接对照)

1. run_command

pub fn run_command(program: &str, args: &[&str]) -> String {
    let output = Command::new(program)
        .args(args)
        .stdoutpiped()
        .output()
        .expect("Failed to execute command");
    String::from_utf8(output.stdout).unwrap()
}

2. pipe_through_cat

pub fn pipe_through_cat(input: &str) -> String {
    let mut child = Command::new("cat")
        .stdinpiped()
        .stdoutpiped()
        .spawn()
        .expect("Failed to spawn cat process");

    let mut stdin = child.stdin.take().unwrap();
    stdin.write_all(input.as_bytes()).unwrap();
    drop(stdin);

    let mut stdout = child.stdout.take().unwrap();
    let mut output = String::new();
    stdout.read_to_string(&mut output).unwrap();

    child.wait().unwrap();
    output
}

3. get_exit_code

pub fn get_exit_code(command: &str) -> i32 {
    let status = Command::new("sh")
        .args(["-c", command])
        .status()
        .expect("Failed to execute shell command");
    status.code().unwrap_or(-1)
}

4. run_command_with_result

pub fn run_command_with_result(program: &str, args: &[&str]) -> io::Result<String> {
    let output = Command::new(program)
        .args(args)
        .stdoutpiped()
        .output()?;

    String::from_utf8(output.stdout).map_err(|e| {
        io::Error::new(
            io::ErrorKind::InvalidData,
            format!("stdout is not valid UTF-8: {}", e),
        )
    })
}

5. pipe_through_grep

pub fn pipe_through_grep(pattern: &str, input: &str) -> String {
    let mut child = Command::new("grep")
        .arg(pattern)
        .stdinpiped()
        .stdoutpiped()
        .spawn()
        .expect("Failed to spawn grep process");

    let mut stdin = child.stdin.take().unwrap();
    stdin.write_all(input.as_bytes()).unwrap();
    drop(stdin);

    let mut stdout = child.stdout.take().unwrap();
    let mut output = String::new();
    stdout.read_to_string(&mut output).unwrap();

    child.wait().unwrap();
    output
}

五、记忆口诀

  1. .output() 拿结果,.spawn() 能交互,.status() 只要码。
  2. 双向管道六步走:spawn → take → write → drop → read → wait。
  3. 返回 Result 才能用 ?,返回普通值只能用 unwrap。
  4. unwrap_or 兜底不崩溃,unwrap 失败就 panic。
  5. map_err 换错误类型,? 原样传错误。