tokioで外部コマンド実行

この記事は tokio 0.3.x に対応しています

# はじめに
Rust で `ls` 等の外部コマンドを実行したいときは [std::process::Command](https://doc.rust-lang.org/std/process/struct.Command.html) が使えます。

下記の例では `spawn()` を使ってコマンドを子プロセスで実行します。標準入力と標準出力は inherit されます。つまり、`cargo run` を実行しているターミナルに結果が出力されます。

```rust
use std::process::Command;

// 子プロセスとして実行します
Command::new("ls")
    .args(&["-l", "-a"])
    .spawn()
    .expect("failed to start `ls`");
```

コマンドの出力結果をプログラム中で使いたい場合は `output()` を使ってコマンドを実行すると便利です。このときは `output()` でコマンドの出力を待機します。

```rust
use std::process::Command;

// コマンドの結果を待機します
let output = Command::new("ls")
    .args(&["-l", "-a"])
    .output()
    .expect("failed to start `ls`");
println!("{}", String::from_utf8_lossy(&output.stdout));
```

`spawn()` を使ってコマンドを実行する場合でコマンドの出力結果をプログラム中で使いたければ、[std::process::ChildStdout](https://doc.rust-lang.org/std/process/struct.ChildStdout.html) を取得しましょう。

```rust
use std::process::{Command, Stdio};
use std::io::prelude::*;
use std::io::BufReader

// 子プロセスとして実行します
let mut child = Command::new("ls")
    .args(&["-l", "-a"])
    .stdout(Stdio::piped())
    .spawn()
    .expect("failed to start `ls`");
// stdout への出力のハンドラを取得します
let mut stdout = child.stdout.take().unwrap();

// BufReader をかませることで 1 行ずつ実行結果を取り出します
let reader = BufReader::new(stdout);
for line in reader.lines() {
    println!("{}", line?);
}
```

`std::process::Command` は `ls` などのすぐ終わるコマンドを実行するときは向いていますが、コマンドの実行や結果の取得時に処理をブロックしてしまうので、実行に時間がかかるようなコマンドを実行するときには不向きだと思います。そこで、[tokio::process::Command](https://docs.rs/tokio/0.2.6/tokio/process/struct.Command.html)  を使って非同期 Rust で書き直してみましょう。


# `tokio::process::Command` によるコマンドの実行
```bash
bash -c 'for i in {0..10}; do echo $i; sleep 1; done'
```

というコマンドを実行することを考えましょう。理想的には 1 秒おきに

```
0
1
2
3
4
5
6
7
8
9
```

と出力されるはずです。

## コマンドの出力結果を待機する
`tokio::process::Command` は `std::process::Command` と同じような API を持ちますが、コマンドの出力結果をみる系のメソッドは `Future` を返すようになっています。ひとまず、 `output()` を使ってみましょう。

```rust
use tokio::process::Command;

// コマンドの結果を待機します
let output = Command::new("bash")
    .arg("-c")
    .arg("for i in {0..10}; do echo $i; sleep 1; done")
    .output()
    .await
    .expect("failed to execute sleep");
println!("{:?}", String::from_utf8_lossy(&output.stdout));
```

使い心地は `std::process::Command` と同じです。コマンドの実行結果全体の出力を `await` しているので、10 秒後に結果が表示されます。

## コマンドの出力結果を1行ずつ取得する
`std::process::Command` でやったものをもとに tokio で書き直してみましょう。コマンドを `spawn()` することで、子プロセスで実行してくれます。下記の例では `stdout` を `FramedRead`[^1] に渡しています。こうすることで、コマンドの出力結果を 1 行ずつ yield してくれるような Stream として扱うことができるようになります。

[^1]: tokio 0.2 から [`tokio-util` クレート](https://crates.io/crates/tokio-util) に分離されました

```rust
use std::process::Stdio;
use tokio::process::Command;
use tokio_util::codec::{FramedRead, LinesCodec};
use futures::prelude::*;

// 1 行ずつ結果を読み取ります
let mut child = Command::new("bash")
    .arg("-c")
    .arg("for i in {0..10}; do echo $i; sleep 1; done")
    .stdout(Stdio::piped())
    .spawn()
    .expect("failed to start sleep");
// stdout への出力のハンドラを取得します
let stdout = child.stdout.take().unwrap();
// stdout の出力を Stream に変換します
let mut reader = FramedRead::new(stdout, LinesCodec::new());

while let Some(line) = reader.next().await {
    println!("{}", line?);
}
```

## コマンドの実行を中断する
コマンドの実行を途中で中止したいときもあるかと思います。Future の処理を中断する場合、Future を単に drop してあげれば OK です。しかし、`Command` の場合はこれだけだと不十分です。一般的な Future は drop されたときにリソースの解放をしてくれるため、`Command` の実行も中止してくれそうな感じがしますが、[`std::process` の挙動に合わせるため drop してもコマンドの実行は中止されなくなりました](https://github.com/tokio-rs/tokio/issues/1771)。drop したときに子プロセスで動いてるコマンドを kill するには、`kill_on_drop(true)` とします。

```rust
use std::process::Stdio;
use std::time::Duration;
use tokio::process::Command;
use tokio::time::sleep;
use tokio_util::codec::{FramedRead, LinesCodec};
use futures::prelude::*;

// 途中でコマンドの実行を中止します
let mut child = Command::new("bash")
    .kill_on_drop(true)
    .arg("-c")
    .arg("for i in {0..10}; do echo $i; sleep 1; done")
    .stdout(Stdio::piped())
    .spawn()
    .expect("failed to start sleep");
let stdout = child.stdout.take().unwrap();
let mut reader = FramedRead::new(stdout, LinesCodec::new());

tokio::spawn(async move {
    // 5 秒後にコマンドの実行を中止します
    sleep(Duration::from_secs(5)).await;
    drop(child);
});

while let Some(line) = reader.next().await {
    println!("{}", line?);
}
```