Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

外部コマンド

外部コマンドを実行して stdout を処理する

std-badge cat-os-badge

外部の Command を使って git log --oneline を実行し、Output の status を調べてコマンドが成功したかどうかを判定します。コマンドの出力は [String::from_utf8] を使って [String] として取得します。

use anyhow::{Result, anyhow};
use std::process::Command;

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

    if output.status.success() {
        let raw_output = String::from_utf8(output.stdout)?;
        let lines = raw_output.lines();
        println!("Found {} lines", lines.count());
        Ok(())
    } else {
        return Err(anyhow!("Command executed with failing error code"));
    }
}

stdin を渡して外部コマンドを実行し、エラーコードを確認する

std-badge cat-os-badge

外部の Command を使って python インタープリタを起動し、実行するための Python 文を渡します。その文の Output をその後で解析します。

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

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

    child.stdin
        .as_mut()
        .ok_or_else(|| anyhow!("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!("Found {} unique words:", words.len());
        println!("{:#?}", words);
        Ok(())
    } else {
        let err = String::from_utf8(output.stderr)?;
        return Err(anyhow!("External command failed:\n {}", err));
    }
}

パイプでつないだ外部コマンドを実行する

std-badge cat-os-badge

現在の作業ディレクトリ内で、サイズが大きい上位 10 個のファイルとサブディレクトリを表示します。これは次を実行するのと同等です: du -ah . | sort -hr | head -n 10

Command はプロセスを表します。子プロセスの出力は、親プロセスと子プロセスの間の Stdio::piped でキャプチャされます。

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

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!(
                "Top 10 biggest files and directories in '{}':\n{}",
                directory.display(),
                String::from_utf8(head_stdout.stdout).unwrap()
            );
        }
    }

    Ok(())
}

子プロセスの stdout と stderr の両方を同じファイルにリダイレクトする

std-badge cat-os-badge

子プロセスを生成し、stdoutstderr を同じファイルにリダイレクトします。パイプで接続した外部コマンドを実行する と同じ考え方ですが、process::Stdio は指定したファイルに書き込みます。File::try_clonestdoutstderr に対して同じファイルハンドルを参照します。これにより、両方のハンドルが同じカーソル位置で書き込むことが保証されます。

以下のレシピは、Unix シェルコマンド ls . oops >out.txt 2>&1 を実行するのと同等です。

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(())
}

子プロセスの出力を継続的に処理する

std-badge cat-os-badge

外部コマンドを実行して stdout を処理する では、 外部 Command が終了するまで処理は開始されません。 以下のレシピでは Stdio::piped を呼び出してパイプを作成し、 BufReader が更新されるたびに stdout を継続的に読み取ります。

以下のレシピは、Unix シェルコマンド journalctl | grep usb と等価です。

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

fn main() -> Result<(), Error> {
    let stdout = Command::new("journalctl")
        .stdout(Stdio::piped())
        .spawn()?
        .stdout
        .ok_or_else(|| Error::new(ErrorKind::Other,"Could not capture standard output."))?;

    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(())
}

環境変数を読み取る

std-badge cat-os-badge

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(())
}