Question
In Rust, how can I run a system command such as ls or fuser? I also want to know how to capture the command's output, including standard output and possibly other result details such as the exit status.
Short Answer
By the end of this page, you will understand how to run external system commands in Rust using std::process::Command, how to capture their output, how to check whether they succeeded, and how to handle common errors safely in real programs.
Concept
Rust provides the std::process::Command type for launching external programs. This is the standard way to invoke system commands such as ls, fuser, grep, or any executable available on the system.
When you run an external command, there are a few important parts to understand:
- The program: the executable you want to run
- Arguments: the values passed to that program
- Exit status: whether the command succeeded or failed
- Standard output (
stdout): normal output from the command - Standard error (
stderr): error messages from the command
In Rust, Command lets you:
- start a command and wait for it to finish
- capture its output
- pass arguments safely
- configure input and output streams
- inspect the exit code
This matters in real programming because many tools and workflows depend on external programs. For example:
- calling Git from a Rust CLI tool
- invoking image or video processing tools
- running shell utilities in deployment scripts
- integrating with system-level commands on Linux or macOS
A key detail is that Command runs a program directly. It does not automatically run through a shell like Bash. That means shell features such as pipes (|), wildcards (*), and redirection (>) do not work unless you explicitly invoke a shell yourself.
Mental Model
Think of Command as filling out a form to ask the operating system to launch another program.
- The program name is the person you want to contact.
- The arguments are the instructions you give them.
- stdout is their normal reply.
- stderr is their complaint or warning.
- The exit status is their final signal saying whether the task succeeded.
Rust acts like a careful assistant: instead of sending one big shell string, it keeps the program and each argument separate. This avoids many quoting and escaping mistakes.
Syntax and Examples
The most common API is std::process::Command.
Basic syntax
use std::process::Command;
fn main() {
let status = Command::new("ls")
.arg("-l")
.status()
.expect("failed to run ls");
println!("Exit status: {status}");
}
Command::new("ls")chooses the program.arg("-l")adds one argument.status()runs the command and waits for it to finish.expect(...)handles the case where the program could not be started
Capture command output
use std::process::Command;
fn main() {
let output = Command::new("ls")
.arg()
.()
.();
(, output.status.());
(, ::(&output.stdout));
(, ::(&output.stderr));
}
Step by Step Execution
Consider this example:
use std::process::Command;
fn main() {
let output = Command::new("echo")
.arg("hello")
.output()
.expect("failed to run echo");
let text = String::from_utf8_lossy(&output.stdout);
println!("Captured: {}", text);
}
Here is what happens step by step:
-
Command::new("echo")- Rust prepares to run the external program named
echo.
- Rust prepares to run the external program named
-
.arg("hello")- One argument is added, so the final command is effectively
echo hello.
- One argument is added, so the final command is effectively
-
.output()- Rust starts the program.
- Rust waits until it finishes.
- Rust collects:
Real World Use Cases
External commands are often used when Rust programs need to interact with the operating system or existing tools.
Common uses
-
CLI wrappers
- A Rust tool may call
git,docker, orkubectland process the result.
- A Rust tool may call
-
System administration scripts
- Run commands like
ls,ps,df, orfuserand inspect their output.
- Run commands like
-
Build and deployment tools
- Execute external build steps, package managers, or shell utilities.
-
Data processing pipelines
- Use existing tools for search, compression, or file conversion.
-
Media and document processing
- Call tools like
ffmpeg,pandoc, or ImageMagick from Rust.
- Call tools like
Example: checking Git version
std::process::Command;
() {
= Command::()
.()
.()
.();
output.status.() {
(, ::(&output.stdout));
} {
();
}
}
Real Codebase Usage
In real Rust codebases, developers usually wrap command execution in helper functions so the rest of the application stays clean and testable.
Common patterns
Guard clauses for failure
Check whether the command launched and whether it succeeded before using the output.
use std::process::Command;
fn run_ls() -> Result<String, String> {
let output = Command::new("ls")
.arg("-l")
.output()
.map_err(|e| format!("Could not start ls: {e}"))?;
if !output.status.success() {
return Err(String::from_utf8_lossy(&output.stderr).into_owned());
}
Ok(String::from_utf8_lossy(&output.stdout).into_owned())
}
Validation before execution
Programs often validate user input before passing it to a command.
Common Mistakes
Beginners often confuse process execution with shell execution. Here are the most common problems.
1. Expecting shell syntax to work automatically
This does not work as many beginners expect:
use std::process::Command;
fn main() {
let output = Command::new("ls -l")
.output()
.expect("failed");
println!("{:?}", output);
}
Why it is wrong
Command::new expects the executable name only. It tries to find a program literally named ls -l.
Correct version
use std::process::Command;
fn main() {
let output = Command::new("ls")
.arg("-l")
.output()
.expect("failed");
println!(, ::(&output.stdout));
}
Comparisons
| Approach | What it does | Captures output? | Best use |
|---|---|---|---|
Command::new(...).status() | Runs a command and returns only the exit status | No | When you only care whether it succeeded |
Command::new(...).output() | Runs a command and captures stdout, stderr, and status | Yes | When you need to read command output |
Command::new(...).spawn() | Starts a command without waiting immediately | Not by default | Long-running processes or interactive control |
status() vs output()
use std::process::Command;
fn main() {
= Command::()
.()
.();
();
}
Cheat Sheet
Quick reference
Run a command
use std::process::Command;
let status = Command::new("ls")
.arg("-l")
.status()?;
Capture output
use std::process::Command;
let output = Command::new("ls")
.arg("-l")
.output()?;
Read stdout and stderr
let stdout = String::from_utf8_lossy(&output.stdout);
let stderr = String::from_utf8_lossy(&output.stderr);
Check success
if output.status.success() {
// command succeeded
}
FAQ
How do I run a terminal command in Rust?
Use std::process::Command. Create a command with Command::new("program"), add arguments with .arg() or .args(), then call .status() or .output().
How do I capture stdout from a command in Rust?
Call .output() and read output.stdout. Convert it to text with String::from_utf8_lossy(&output.stdout).
How do I capture stderr in Rust?
output.stderr contains the captured error output. Convert it the same way as stdout.
What is the difference between status() and output() in Rust?
status() returns only the exit status. output() returns the exit status plus captured stdout and stderr.
Why does Command::new("ls -l") fail?
Because Rust looks for an executable literally named ls -l. Pass the program and its arguments separately: .
Mini Project
Description
Build a small Rust utility that runs a system command entered in code, captures its output, and reports whether it succeeded. This demonstrates the core workflow of launching a child process, reading stdout and stderr, and checking the exit status.
Goal
Create a Rust program that runs a command, captures its output, and prints a clear success or failure report.
Requirements
- Run an external command using
std::process::Command. - Pass at least one argument to the command.
- Capture both
stdoutandstderr. - Print whether the command succeeded.
- Show the captured output in a readable text form.
Keep learning
Related questions
Accessing Cargo Package Metadata in Rust
Learn how to read Cargo package metadata like version, name, and authors in Rust using compile-time environment macros.
Associated Types vs Generic Type Parameters in Rust: When to Use Each
Learn when to use associated types vs generic parameters in Rust traits, with clear rules, examples, and practical API design advice.
Convert an Integer to a String in Rust
Learn the current Rust way to convert integers to strings, why `to_str()` no longer works, and when to use `to_string()` or `format!`.