Environment and Process Management

Spawning External Commands

Spawning external commands creates a new operating system child process to execute an external program. Rust's std::process::Command provides three distinct execution methods—status(), output(), and spawn()—that differ in whether they block the parent thread and how they connect the child's standard I/O streams.

                        Command::new("program")
                                   |
        +--------------------------+--------------------------+
        |                          |                          |
        v                          v                          v
   .status()                  .output()                  .spawn()
   ---------                  ---------                  --------
   Blocks until exit          Blocks until exit          Returns immediately!
   Returns: io::Result<       Returns: io::Result<       Returns: io::Result<
     ExitStatus>                Output>                    Child>
   Default Stdio:             Default Stdio:             Default Stdio:
     stdin:  Inherit            stdin:  Null               stdin:  Inherit
     stdout: Inherit            stdout: Piped              stdout: Inherit
     stderr: Inherit            stderr: Piped              stderr: Inherit

Comparing status(), output(), and spawn()

Method Return Type Blocking? Default stdin Default stdout / stderr Primary Use Case
status() io::Result<ExitStatus> Yes Stdio::inherit() Stdio::inherit() Running a command whose output should stream directly to the user's terminal, checking only whether it succeeded.
output() io::Result<Output> Yes Stdio::null() Stdio::piped() Running a command to capture its complete stdout and stderr buffers into memory.
spawn() io::Result<Child> No Stdio::inherit() Stdio::inherit() Running a background process, streaming pipes interactively, or running multiple children concurrently.

Capturing Output with .output()

Command::output() waits for the child process to finish and collects all bytes written to its standard output and standard error streams into Output { status, stdout, stderr }.

use std::io;
use std::process::Command;

fn main() -> io::Result<()> {
    let output = Command::new("rustc")
        .arg("--version")
        .output()?;

    if output.status.success() {
        let text = String::from_utf8_lossy(&output.stdout);
        println!("Captured stdout ({} bytes): {}", output.stdout.len(), text.trim());
    } else {
        let err = String::from_utf8_lossy(&output.stderr);
        eprintln!("Command failed: {err}");
    }

    Ok(())
}
Captured stdout (35 bytes): rustc 1.85.0 (4d91de4e4 2025-02-17)

Non-Blocking Execution with .spawn()

Command::spawn() starts the child process and returns a Child handle immediately so the parent can perform concurrent work, poll with try_wait(), or wait later with wait().

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

fn main() -> io::Result<()> {
    let mut child = Command::new("rustc")
        .arg("-V")
        .stdout(Stdio::null())
        .spawn()?;

    // Poll without blocking via `try_wait()`
    match child.try_wait()? {
        Some(status) => println!("Already exited with: {status}"),
        None => {
            // Still running; block until completion
            let final_status = child.wait()?;
            println!("Finished after wait, success: {}", final_status.success());
        }
    }

    Ok(())
}
Finished after wait, success: true

Avoiding Shell Injection

Command::new executes the target binary directly via the OS kernel (posix_spawnp / fork+execvp on Unix, CreateProcessW on Windows) without invoking a system shell (/bin/sh or cmd.exe).

Because no shell interprets the arguments:

  • Shell metacharacters such as ;, |, &&, $(...), and > passed inside .arg() are delivered verbatim as literal strings to the target executable, eliminating shell command injection vulnerabilities.
  • Shell built-ins (such as cd, export, or dir on Windows) and glob patterns (*.rs) are not expanded automatically unless you explicitly invoke sh -c or cmd /C.