Process »
- Functions:
abort() -> !
exit(code: i32) -> !
id() -> u32
Command struct to run a shell command with args
- Use
.output() -> Result<Output> to execute the command and wait for it to finish and colelct all of its output
- By default, stdout/stderr are captured (piped)
- stdin is not inherited and any attempt by the child process to read from stdin will close stream
- With
.stdin(Stdio::inherit()) it can inherit parent stdin
- Use
.spawn() -> Result<Child> to execute a command as a child process but only return a handle to it
- Recommend to call
.wait() or .wait_with_output() to ensure OS to release resources
.wait() -> Result<ExitStatus>
.wait_with_output() -> Result<Output> simultaneously waits for the child to exit and collect all remaining output
- Essentially,
.spawn().wait_with_output() is the same as .output()
- By default, stdin/stdout/stderr are inherited from parent
- Use
.stdin(), .stdout() and .stderr() to config I/O stream
process::Stdio::{null(), inherit(), piped()}
Stdio::from impl for ChildStderr, ChildStdin, ChildStdout, and File
Child struct (from spawn) also has public fields stdin, stdout and stderr all of which are Option
- Use
take to take ownership to avoid partially moving the child
- e.g.
let stdin = child.stdin.take().unwrap();
// use output
// default stdout is piped, so it's not printed to console
Command::new("sh")
.arg("-c")
.arg("echo hello")
.output()
.expect("failed to execute process");
println!("status: {}", output.status);
io::stdout().write_all(&output.stdout).unwrap();
io::stderr().write_all(&output.stderr).unwrap();
assert!(output.status.success());
// use spawn
// default stdout is inherit, so this will print out to console
Command::new("ls")
.args(["-l", "-a"])
.spawn()
.expect("ls command failed to start")
.wait()?; // call wait to release resource
// run a pipe `du -ah .| sort -hr | head -n 10`
use std::process::{Command, Stdio};
fn main() -> Result<()> {
let directory = std::env::current_dir()?;
// need to use mut
let mut du_output_child = Command::new("du")
.arg("-ah")
.arg(&directory)
.stdout(Stdio::piped()) // pipe stdout
.spawn()?;
if let Some(du_output) = du_output_child.stdout.take() {
let mut sort_output_child = Command::new("sort")
.arg("-hr")
.stdin(du_output) // pipe du_output to stdin of sort
.stdout(Stdio::piped()) // pipe stdout
.spawn()?;
du_output_child.wait()?; // release du
if let Some(sort_output) = sort_output_child.stdout.take() {
let head_output_child = Command::new("head")
.args(&["-n", "10"])
.stdin(sort_output)
.stdout(Stdio::piped())
.spawn()?; // this can be replaced with .output()
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 stdout and stderr to file
use std::fs::File;
use std::io::Error;
use std::process::{Command, Stdio};
fn main() -> Result<(), Error> {
let outputs = File::create("out.txt")?;
let errors = outputs.try_clone()?;
Command::new("ls")
.args(&[".", "oops"])
.stdout(outputs) // same as Stdio::from(outputs)
.stderr(Stdio::from(errors)) // same as `errors`
.spawn()?
.wait_with_output()?; // same as .output()
Ok(())
}
Environment »
env is used to obtain env variables including pwd
std::env::consts::
ARCH: CPU
EXE_EXTENSION: exe
EXE_SUFFIX: .exe
FAMILY: unix
OS: linux
- Notable Functions:
::args() -> Args
::current_dir() -> Result<PathBuf>
::set_current_dir(&path) -> Result<()>
::temp_dir() -> Result<PathBuf>
::var<K: AsRef<OsStr>>(key: K) -> Result<String, VarError>
::vars() -> Vars to iterator
::split_paths(&path) -> SplitPaths<'_> to iterator
OsStr and Path
OsStr and OsString
OsStr and OsString deal with non UTF-8 coded Strings
OsStr is akin to &str and OsString is owned akin to String
- Common methods include
new, push (can push more than char), with_capacity, reserve
- Conversion:
OsStr to OsString: into_os_string(self: Box<OsStr>), to_os_string(&self)
OsStr to &str: to_str(&self) -> Option<&str>
OsStr to String: to_string_lossy(&self) -> Cow<'_, str>
OsString to OsStr: as_os_str(&self) -> &OsStr, into_string(self) -> Result<String, OsString>
OsString impl From<String>, i.e. OsString::from(s: String)
- Both impl
AsRef<Path>
Path and PathBuf
std::path is used to parse or build Path
Path and PathBuf are just thin wrappers of OsStr and OsString with convenient functions
pub struct PathBuf {
inner: OsString,
}
pub struct PathBuf {
inner: OsString,
}
std::path::Path is akin to &str
Path::new("/path/to/some")
.components() -> Components returns an iter
.read_dir() -> Result<ReadDir>
.to_path_buf() -> PathBuf
.to_str() -> Option(&str)
path.parent() -> Option<&Path>
path.ancestors() -> Ancestors<'_> returns iter with full path
path.file_name() -> Option<&OsStr>
path.is_absolute() and path.is_relative()
path1.join(path2)
path.display() for printing paths
std::path::PathBuf is akin to String
PathBuf::new() note this one does not take parameters
PathBuf::from("/some/path")
.pop() and .push("/path")
.as_path() -> Path
- Implemented
Deref and can use Path methods
File System »
std::fs is used to open/close/create/delete files
- Notable functions:
::copy(from, to) -> Result<u64>
::create_dir(&path) -> Result<()> or use ::create_dir_all
::metadata(&path) -> Result<MetaData>
::read_dir(&path) -> Result<ReadDir> return an iterator
::remove_dir(&path) and ::remove_dir_all(path) both -> Result<()>
::remove_file(&path) -> Result<()>
::set_permissions(&path, permissions) -> Result<()>
::canonicalize(path) -> Result<PathBuf> eq to realpath() in Unix
- Convenient functions for
Read
::read(&path) -> Result<Vec<u8>> open a file and read to end
::read_to_string(&path) -> Result<String> faster than reading into a String::new()
- Convenient fn for
Write
::write(path, contents) -> Result<()> will replace file if exists
std::fs::File struct
::create(path) -> Result<File>
::open(path) -> Result<File>
.metadata() -> Result<MetaData> similar to fs::metadata(path)
.set_permissions(permission) -> Result<()> similar to fs::set_permissions
- Implement
std::io::Read
.read(&mut self, buf: &mut [u8]) -> Result<usize>
.read_to_end(&mut self, buf: &mut [u8]) -> Result<usize>
.read_to_string(&mut self, buf: &mut [u8]) -> Result<usize>
- Implement
std::io::Seek
.seek(&mut self, pos: SeekFrom) -> Result<u64>
- Implement
std::io::Write
.write(&mut self, buf: &[u8]) -> Result<usize>
.write_all(&mut self, buf: &[u8]) -> Result<()>
.write_fmt(&mut self, fmt: Arguments<'_>) -> Result<()>
.flush() flush the output streamt
OpenOptions
std::fs::OpenOptions struct set Options and flags to config how a file is opened
- First call
new()
- Chain calls to methods to set each option
- Call
open passing the path of file
- Return
io::Result<File>
.new() // return Self
.read(bool) // the rest take bool other than open
.write(bool) // if file already exists, will overwrite without truncating
.append(bool) // .write(true).append(true) is the same as .append(true)
.truncate(bool) // file must be opened with write access for this to work
.create(bool) // create a new, or open an existing one, used after .write or .append
.create_new(bool) // only create, failing if one exists
.open(path) // return Result<File>
// File::open
OpenOptions::new().read(true).open(path.as_ref())
// File::create
OpenOptions::new().write(true).create(true).truncate(true).open(path.as_ref())
std::io
::copy(&mut reader, &mut writer) -> Result<u64>
::empty() -> Empty
::stdin() -> Stdin creates a new handle to stdin, also ::stdin_locked()
::stdout() -> Stdout
::stderr() -> Stderr
Read and Write Traits
Read trait
pub trait Read {
// required; low-level, avoid using directly
fn read(&mut self, buf: &mut [u8]) -> Result<usize>;
// provided
fn read_vectored(&mut self, bufs: &mut [IoSliceMut<'_>]) -> Result<usize> { ... }
fn is_read_vectored(&self) -> bool { ... }
unsafe fn initializer(&self) -> Initializer { ... }
fn read_to_end(&mut self, buf: &mut Vec<u8>) -> Result<usize> { ... }
fn read_to_string(&mut self, buf: &mut String) -> Result<usize> { ... }
fn read_exact(&mut self, buf: &mut [u8]) -> Result<()> { ... }
fn by_ref(&mut self) -> &mut Self
where
Self: Sized,
{ ... }
fn bytes(self) -> Bytes<Self>
where
Self: Sized,
{ ... }
fn chain<R: Read>(self, next: R) -> Chain<Self, R>
where
Self: Sized,
{ ... }
fn take(self, limit: u64) -> Take<Self>
where
Self: Sized,
{ ... }
}
std::io::BufRead trait is better to read into buffer or reading line-by-line
BufReader::new(r: Read) turns a reader(e.g., File) into BufReader
pub trait BufRead: Read {
// required
fn fill_buf(&mut self) -> Result<&[u8]>;
fn consume(&mut self, amt: usize);
// provided
fn has_data_left(&mut self) -> Result<bool> { ... }
fn read_until(&mut self, byte: u8, buf: &mut Vec<u8>) -> Result<usize> { ... }
fn read_line(&mut self, buf: &mut String) -> Result<usize> { ... }
fn split(self, byte: u8) -> Split<Self>
where
Self: Sized,
{ ... }
fn lines(self) -> Lines<Self>
where
Self: Sized,
{ ... }
}
- Many readers including
.lines() returns itr that produces Result
// collect Results
let lines = reader.lines().collect::<io::Result<Vec<String>>>()?;
// this takes advantage of `FromIterator` for `Result`
impl<T, E, C> FromIterator<Result<T, E>> for Result<C, E>
where C: FromIterator<T>
Write trait
std::io::BufWriter does not add new write method
BufWriter::new(W) -> BufWriter
.write(buf: &[u8]) -> Result<usize>
- call
.flush() -> Result<usize> before doprring to ensure the buffer is empty and check for errors
pub trait Write {
// required, low-level
fn write(&mut self, buf: &[u8]) -> Result<usize>;
// ensures that all intermediately buffered contents reach their dest.
// print! and eprint! do not call flush; call manually
fn flush(&mut self) -> Result<()>;
// provided
fn write_vectored(&mut self, bufs: &[IoSlice<'_>]) -> Result<usize> { ... }
fn is_write_vectored(&self) -> bool { ... }
fn write_all(&mut self, buf: &[u8]) -> Result<()> { ... }
fn write_all_vectored(&mut self, bufs: &mut [IoSlice<'_>]) -> Result<()> { ... }
fn write_fmt(&mut self, fmt: Arguments<'_>) -> Result<()> { ... }
fn by_ref(&mut self) -> &mut Self
where
Self: Sized,
{ ... }
}
Common Read and Write implementors
Read
├── Stdin
├── File
├── TcpStream
├── process::ChildStdout
├── process::ChildStderr
├── BufRead
├── BufReader<R>
├── Cursor<&[u8]>
├── StdinLock
├── &[u8]
Write
├── Stdout
├── Stderr
├── File
├── TcpStream
├── Vec<u8>
├── BufWriter<W>
├── process::ChildStdin
// Vec<u8> impl `Write`
// String does not impl `Write`
// to convert to String, use From
String::from_utf8(vec: Vec<u8>);
String::from_utf8_lossy(vec);
Seek trait
pub trait Seek {
// required
fn seek(&mut self, pos: SeekFrom) -> Result<u64>;
// provided
fn rewind(&mut self) -> Result<()> { ... }
fn stream_len(&mut self) -> Result<u64> { ... }
fn stream_position(&mut self) -> Result<u64> { ... }
}
// SeekFrom is an enum
pub enum SeekFrom {
Start(u64), // sets the offset to the provided no. of bytes
End(i64), // sets the offset to the size of this obj, plus the num.
Current(i64), // sets the offset to the current position plus the num.
}
// seeking within a file is slow
let mut f = File::open("foo.txt")?;
// move the cursor 42 bytes from the start of the file
f.seek(SeekFrom::Start(42))?;
Stdin/Stdout/Stderr
io::{stdin(), stdout(), stderr()} constructs a new handle returning Stdin, Stdout and Stderr
Stdin impl Read, also has a convenient fn read_line which is self.lock().read_line()
StdinLock impl both Read and BufRead
.lock() -> StdinLock<'_> locks the handle, returning a guard that implements Read and BufRead
self.into_locked(self) -> StdinLock<``static> consumes the handle
.read_line(buf: &mut String) -> Result<usize> shortcut to self.lock().read_line(buf)
self.lines() -> Lines shortcut to self.lock().lines()
std::io::Stdout and std::io::Stderr both impl Write
self.lock() -> StdoutLock<``_> locks the handle
self.into_locked(self) -> StdoutLock<``static> consumes the handle
- Comparing to
process::Stdio
- In
process, {stdin, stdout, stderr} is the public struct of Child
- i.e.,
child.stdin.take().unwrap() will return the stdin of the Child element
- In
process, {stdin(), stdout(), stderr()} are fn of Command to config I/O stream
cmd.stdin(Stdio::piped()).spawn()?
- In short,
process uses std* to config or obtain I/O stream of the child process
- Whereas,
io use std*() to construct a handle for the I/O of the currentprocess
// io::stdin().lock() does not work
// because the lock holds a ref to the Stdin value
// Stdin value must be stored that it lives long enough
let stdin = io::stdin();
let lines = stdin.lock().lines(); // ok