External Command
Locate Installed Executables with which
which
⮳ is a Rust equivalent of Unix command "which". It locates installed executables in a cross-platform way.
//! Demonstrates how to use the `which` crate to find the location of //! executables. use std::env; use which::which; fn main() { // Print the current PATH environment variable. println!("PATH = {:?}", env::var_os("PATH")); // Try to find the location of common executables. match which("rustc") { Ok(path) => println!("Found rustc at: {}", path.display()), Err(e) => println!("Could not find rustc: {}", e), } // Try to find a command that likely doesn't exist. match which("some-command-that-probably-doesnt-exist") { Ok(path) => println!("Found command at: {}", path.display()), Err(e) => println!("Could not find command: {}", e), } }
Run an External Command and Process its stdout
Runs git log --oneline
as an external std::process::Command
⮳ and inspects its std::process::Output
⮳ using regex::Regex
⮳ to get the hash and message of the last 5 commits.
//! This example demonstrates how to run an external command, capture its //! output, and parse it. use std::process::Command; use anyhow::Result; use anyhow::bail; use regex::Regex; /// Represents a Git commit with its hash and message. #[derive(PartialEq, Default, Clone, Debug)] struct Commit { hash: String, message: String, } /// Executes the `git log --oneline` command, parses its output, and prints the /// first 5 commits. /// /// # Returns /// /// Returns `Ok(())` if the command executes successfully and the output is /// parsed correctly. Returns an error if the command fails or the output cannot /// be parsed. fn main() -> Result<()> { let output = Command::new("git").arg("log").arg("--oneline").output()?; if !output.status.success() { bail!("Command executed with failing error code."); } let pattern = Regex::new( r"(?x) ([0-9a-fA-F]+) # commit hash (.*) # The commit message", )?; String::from_utf8(output.stdout)? .lines() .filter_map(|line| pattern.captures(line)) .map(|cap| Commit { hash: cap[1].to_string(), message: cap[2].trim().to_string(), }) .take(5) .for_each(|x| println!("{:?}", x)); Ok(()) }
Run an External command, Passing it stdin
, then Check for an Error Code
Opens the python
interpreter using an external std::process::Command
⮳ and passes it a python statement for execution. The std::process::Output
⮳ of statement is then parsed.
//! This example demonstrates how to send input to an external command's //! standard input (stdin) and process its standard output (stdout) and standard //! error (stderr). //! //! It uses the `rev` command, which reverses each line of its input. //! //! The example sends the string "1234 56789" to `rev` and then checks if the //! output is successful. If successful, it parses the output, splits it into //! words, converts them to lowercase, and stores them in a HashSet to get //! unique words. use std::collections::HashSet; use std::io::Write; use std::process::Command; use std::process::Stdio; use anyhow::Result; use anyhow::anyhow; use anyhow::bail; fn main() -> Result<()> { let mut child = Command::new("rev") .stdin(Stdio::piped()) .stderr(Stdio::piped()) .stdout(Stdio::piped()) .spawn()?; child .stdin .as_mut() .ok_or(anyhow!("Child process' stdin has not been captured!"))? .write_all(b"1234 56789")?; let output = child.wait_with_output()?; if output.status.success() { let raw_output = String::from_utf8(output.stdout)?; let words = raw_output .split_whitespace() .map(|s| s.to_lowercase()) .collect::<HashSet<_>>(); println!("Found {} unique words:", words.len()); println!("{:#?}", words); } else { let err = String::from_utf8(output.stderr)?; bail!("External command failed:\n {}", err); } Ok(()) }
Run Piped External Commands
Shows up to the 10th biggest files and subdirectories in the current working directory. It is equivalent to running: du -ah . | sort -hr | head -n 10
.
std::process::Command
⮳ represent a process. Output of a child process is captured with a std::process::Stdio::piped
⮳⮳ between parent and child.
#![allow(clippy::single_match)] #![cfg(target_family = "unix")] use std::process::Command; use std::process::Stdio; /// This example demonstrates how to pipe the output of one external command to /// the input of another. /// /// It uses the `du`, `sort`, and `head` commands to find the top 10 largest /// files and directories in the current directory. /// /// The `du` command is used to get the disk usage of each file and directory. /// The `sort` command is used to sort the output of `du` in reverse /// human-readable order. The `head` command is used to get the first 10 lines /// of the sorted output. fn main() -> anyhow::Result<()> { let directory = std::env::current_dir()?; let mut du_output_child = Command::new("du") .arg("-ah") .arg(&directory) .stdout(Stdio::piped()) .spawn()?; if let Some(du_output) = du_output_child.stdout.take() { let mut sort_output_child = Command::new("sort") .arg("-hr") .stdin(du_output) .stdout(Stdio::piped()) .spawn()?; du_output_child.wait()?; match sort_output_child.stdout.take() { Some(sort_output) => { let head_output_child = Command::new("head") .args(["-n", "10"]) .stdin(sort_output) .stdout(Stdio::piped()) .spawn()?; let head_stdout = head_output_child.wait_with_output()?; sort_output_child.wait()?; println!( "Top 10 biggest files and directories in '{}':\n{}", directory.display(), String::from_utf8(head_stdout.stdout).unwrap() ); } _ => {} } } Ok(()) }
Redirect both the stdout
and stderr
of a Child Process to the Same File
Spawns a child process and redirects std::io::Stdout
⮳ and std::io::Stderr
⮳ to the same file. It follows the same idea as run piped external commands, however std::process::Stdio
⮳ writes to a specified file. std::fs::File::try_clone
⮳ references the same file handle for std::io::Stdout
⮳ and std::io::Stderr
⮳. It will ensure that both handles write with the same cursor position.
The below recipe is equivalent to run the Unix shell command ls . oops >out.txt 2>&1
.
#![cfg(target_family = "unix")] use std::fs; use std::fs::File; use std::path::Path; use std::process::Command; use std::process::Stdio; /// This example demonstrates how to capture both standard output and standard /// error from an external command and redirect them to the same file. /// /// It uses `ls` command with a non-existent directory "oops" to generate an /// error. Both the standard output (listing of the current directory) and the /// standard error (error message about "oops") will be written to /// "temp/out.txt". fn main() -> Result<(), std::io::Error> { let path = Path::new("temp/out.txt"); let outputs = File::create(path)?; // Creates a new `File` instance that shares the same underlying file handle // as the existing File instance. Reads, writes, and seeks will affect both // `File` instances simultaneously. let errors = outputs.try_clone()?; Command::new("ls") .args([".", "oops"]) .stdout(Stdio::from(outputs)) .stderr(Stdio::from(errors)) .spawn()? .wait_with_output()?; // Print contents of the file. std::fs::read_to_string(path)? .lines() .for_each(|line| println!("{}", line)); Ok(()) }
Continuously Process the Outputs of a Child Process
In Run an external command and process its stdout
, processing doesn't start until the external std::process::Command
is finished. The recipe below calls std::process::Stdio::piped
⮳ to create a pipe, and reads
std::io::Stdout
⮳ continuously as soon as the std::io::BufReader
⮳ is updated.
The below recipe is equivalent to the Unix shell command journalctl | grep usb
.
//! This example demonstrates how to continuously read from the standard output //! of an external command, filter the output, and print the filtered lines. It //! uses the `journalctl` command to read system logs, filters lines containing //! "usb", and prints them to the console. use std::io::BufRead; use std::io::BufReader; use std::io::Error; use std::io::ErrorKind; use std::process::Command; use std::process::Stdio; fn main() -> Result<(), Error> { // NOTE: `systemd` should be installed for this example to work. let stdout = Command::new("journalctl") .stdout(Stdio::piped()) // Spawn the command and capture its standard output. .spawn()? .stdout // Ensure that standard output was captured successfully. .ok_or_else(|| { Error::new(ErrorKind::Other, "Could not capture standard output.") })?; // Create a buffered reader for efficient line-by-line reading. let reader = BufReader::new(stdout); // Iterate over the lines from the reader. reader .lines() // Convert each line to a String, discarding errors. .map_while(Result::ok) // Filter lines that contain the substring "usb". .filter(|line| line.contains("usb")) .for_each(|line| println!("{}", line)); Ok(()) }
Read an Environment Variable
Reads an environment variable via std::env::var
⮳.
//! This example demonstrates how to read an environment variable. //! If the environment variable is not set, a default value is used. use std::env; /// Reads the `PATH` environment variable, or a default value if the variable is /// not set. fn main() { let path = env::var("PATH").unwrap_or("".to_string()); println!("Path: {}", path); }
Run Child Processes Using duct
duct
⮳ is a library for running child processes. duct
⮳ makes it easy to build pipelines and redirect I/O like a shell. At the same time, duct
⮳ helps you write correct, portable code: whitespace is never significant, errors from child processes get reported by default, and a variety of gotchas, bugs, and platform inconsistencies⮳ are handled for you.