1. 外部命令

  需要安装regex库和error-chain库,可通过cargo add regexcargo add error-chain 命令安装

[dependencies]
regex = "1.8.1"
error-chain = "0.12.4"

1.1 运行外部命令并处理 stdout

  将 git log --oneline 作为外部命令 Command 运行,并使用 Regex 检查其 Output,以获取最后 5 次提交的哈希值和消息。

use error_chain::error_chain;

use regex::Regex;
use std::process::Command;

error_chain! {
foreign_links {
Io(std::io::Error);
Regex(regex::Error);
Utf8(std::string::FromUtf8Error);
}
}

#[derive(PartialEq, Default, Clone, Debug)]
struct Commit {
hash: String,
message: String,
}

fn main() -> Result<()> {
let output = Command::new("git").arg("log").arg("--oneline").output()?;

if !output.status.success() {
error_chain::bail!("Command executed with failing error code");
}

let pattern = Regex::new(
r"(?x)
([0-9a-fA-F]+) # 提交的哈希值
(.*) # 提交信息",
)?;

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(())
}
  • 运行cargo run输出
Commit { hash: "f307002", message: "test11" }
Commit { hash: "60d4e16", message: "test12" }
Commit { hash: "7335ab9", message: "test13" }
Commit { hash: "e545eef", message: "test14" }
Commit { hash: "9ed33ac", message: "test15" }

1.2 运行传递 stdin 的外部命令,并检查错误代码

  使用外部命令 Command 打开 python(注:Linux下可能是python3) 解释器,并传递一条 python 语句供其执行,然后解析语句的输出结构体 Output

use error_chain::error_chain;

use std::collections::HashSet;
use std::io::Write;
use std::process::{Command, Stdio};

error_chain! {
errors { CmdError }
foreign_links {
Io(std::io::Error);
Utf8(std::string::FromUtf8Error);
}
}

fn main() -> Result<()> {
let mut child = Command::new("python3")
.stdin(Stdio::piped())
.stderr(Stdio::piped())
.stdout(Stdio::piped())
.spawn()?;

child
.stdin
.as_mut()
.ok_or("Child process stdin has not been captured!")?
.write_all(b"import this; copyright(); credits(); exit()")?;

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!("找到 {} 个独特的词:", words.len());
println!("{:#?}", words);
Ok(())
} else {
let err = String::from_utf8(output.stderr)?;
error_chain::bail!("外部命令失败:\n {}", err)
}
}
  • 运行cargo run输出
找到 127 个独特的词:
{
"now.",
"1995-2001",
"purity.",
... # 此处省略
"complex.",
"may",
}

1.3 运行管道传输的外部命令

  显示当前工作目录中前 10 大的文件和子目录,它等同于运行: du -ah . | sort -hr | head -n 10。每个命令 Command 代表一个进程,子进程的输出是通过父进程和子进程之间的管道 Stdio::piped 捕获的。

use error_chain::error_chain;

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

error_chain! {
foreign_links {
Io(std::io::Error);
Utf8(std::string::FromUtf8Error);
}
}

fn main() -> 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()?;

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()?;

let head_stdout = head_output_child.wait_with_output()?;

sort_output_child.wait()?;

println!(
"“{}”中的前 10 个最大文件和目录:\n{}",
directory.display(),
String::from_utf8(head_stdout.stdout).unwrap()
);
}
}

Ok(())
}
  • 运行cargo run输出
“/root/rustcookbook/ostest”中的前 10 个最大文件和目录:
160M /root/rustcookbook/ostest/target/debug
160M /root/rustcookbook/ostest/target
160M /root/rustcookbook/ostest
124M /root/rustcookbook/ostest/target/debug/deps
18M /root/rustcookbook/ostest/target/debug/build
17M /root/rustcookbook/ostest/target/debug/deps/libregex_syntax-6ee924d67e38f866.rlib
13M /root/rustcookbook/ostest/target/debug/ostest
11M /root/rustcookbook/ostest/target/debug/deps/libregex-62f85974b4cf0dcb.rlib
11M /root/rustcookbook/ostest/target/debug/deps/libaho_corasick-6fc38c3e1c0e1b2d.rlib
9.7M /root/rustcookbook/ostest/target/debug/deps/libbacktrace-75169ab568ebd602.rlib

1.4 将子进程的 stdoutstderr 重定向到同一个文件

  生成子进程并将 stdoutstderr 重定向到同一个文件。它遵循与运行管道传输的外部命令相同的思想,但是 process::Stdio 会将输出写入指定的文件。对 stdoutstderr 而言,File::try_clone 引用相同的文件句柄。它将确保两个句柄使用相同的光标位置进行写入。下面的实例等同于运行 Unix shell 命令 ls . oops >out.txt 2>&1(命令用于将标准输出和标准错误都重定向到名为 out.txt 的文件中)。

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(Stdio::from(outputs))
.stderr(Stdio::from(errors))
.spawn()?
.wait_with_output()?;

Ok(())
}
  • 运行cargo run将创建out.txt,文件内容如下:
ls: cannot access 'oops': No such file or directory
.:
Cargo.lock
Cargo.toml
learn.md
out.txt
src
target

1.5 持续处理子进程的输出

  在运行外部命令并处理 stdout 实例中,直到外部命令 Command 完成,stdout 的处理才开始。下面的实例调用 Stdio::piped 创建管道,并在 BufReader 被更新后立即读取 stdout,持续不断地处理。下面的实例等同于 Unix shell 命令 journalctl | grep usb.

use std::io::{BufRead, BufReader, Error, ErrorKind};
use std::process::{Command, Stdio};

fn main() -> Result<(), Error> {
let stdout = Command::new("journalctl")
.stdout(Stdio::piped())
.spawn()?
.stdout
.ok_or_else(|| Error::new(ErrorKind::Other, "无法捕获标准输出。"))?;

let reader = BufReader::new(stdout);

reader
.lines()
.filter_map(|line| line.ok())
.filter(|line| line.find("usb").is_some())
.for_each(|line| println!("{}", line));

Ok(())
}
  • 运行cargo run将输出:
Feb 25 10:51:07 dynasty sshd[917191]: Invalid user ...
Feb 25 10:51:09 dynasty sshd[917191]: Failed password ...
Feb 25 10:51:11 dynasty sshd[917191]: Disconnected ...
Feb 25 10:55:44 dynasty sshd[917239]: Invalid user ...
Feb 25 10:55:45 dynasty sshd[917239]: Failed password ...
...

1.6 读取环境变量

  通过 std::env::var 读取环境变量。

use std::env;
use std::fs;
use std::io::Error;

fn main() -> Result<(), Error> {
// 从环境变量 `CONFIG` 读取配置路径 `config_path`。
// 如果 `CONFIG` 未设置,采用默认配置路径。
let config_path = env::var("CONFIG").unwrap_or("/etc/myapp/config".to_string());

let config: String = fs::read_to_string(config_path)?;
println!("Config: {}", config);

Ok(())
}
    1. 首先设置一个环境变量:
export CONFIG=out.txt # 将要读取的文件
    1. 运行cargo run将输出:
Config: ls: cannot access 'oops': No such file or directory
.:
Cargo.lock
Cargo.toml
learn.md
out.txt
src
target