Java SE 7のProcessBuilderでリダイレクト

Java SE 7よりJavaから外部コマンドを実行できるProcessBuilderに便利な機能が追加されている。pingを実行してファイルにリダイレクトが簡単にできるなど、派手ではないが嬉しい機能追加である。以下では、追加された機能を使って色々なpingを実行してみる。

ping結果をパイプ経由の入力ストリームから取得

ProcessBuilder pb = new ProcessBuilder("ping", "-c", "3", "127.0.0.1");

try {
  Process p = pb.start();
  // ping が完了するのを待つ
  p.waitFor();

  // 実行結果を取得するストリームの種別を出力
  System.out.println(pb.redirectInput());

  try (BufferedReader br = new BufferedReader(
          new InputStreamReader(p.getInputStream()))) {
    // ping結果の出力
    for(String line = br.readLine(); line != null; line = br.readLine()) {
      System.out.println(line);
    }
  }
} catch (IOException | InterruptedException e) {
  // 例外ハンドリング処理
}

Mac OS X 10.7.5 で試すと、以下のように出力される。

PIPE
PING 127.0.0.1 (127.0.0.1): 56 data bytes
64 bytes from 127.0.0.1: icmp_seq=0 ttl=64 time=0.068 ms
64 bytes from 127.0.0.1: icmp_seq=1 ttl=64 time=0.109 ms
64 bytes from 127.0.0.1: icmp_seq=2 ttl=64 time=0.175 ms

--- 127.0.0.1 ping statistics ---
3 packets transmitted, 3 packets received, 0.0% packet loss
round-trip min/avg/max/stddev = 0.068/0.117/0.175/0.044 ms

一番最初にPIPEと表示される。これはProcessBuilderのデフォルトの挙動で、ProcessBuilderの実行によって生成された子プロセスは、結果をパイプに書き込むため、親プロセスのJavaからパイプに対する入力ストリーム経由で結果を取得できる。

ping結果をファイルに書き込む

かつては『>』を使ってコマンド実行結果をファイルに書き込もうとしてハマることも多くあったが、Java SE 7ではファイルにリダイレクトするためのAPIが整備されている。

ProcessBuilder pb = new ProcessBuilder("ping", "-c", "3", "127.0.0.1");
File log = new File("ping.log");

// 標準エラーを標準出力にマージする
pb.redirectErrorStream(true);
// 出力のリダイレクト先にファイルを指定 (上書き)
pb.redirectOutput(log);
// ファイルに追記する場合
// pb.redirectOutput(Redirect.appendTo(log));

try {
  Process p = pb.start();
  // ping が完了するのを待つ
  p.waitFor();
} catch (IOException | InterruptedException e) {
  // 例外ハンドリング処理
}

上記のコード例では、pingの出力結果をjava起動ディレクトリのping.logに書き出している。ProcessBuilder.redirectErrorStream(true)を設定することによって、標準出力と標準エラー出力をマージできるので、どちらも同一のファイルに書き出したい場合に便利。また、コメントアウトされている部分のように、ProcessBuilder.Redirect.appentTo(File file)によってファイルを設定すると上書きではなく『>>』のように追記することもできる。

ping結果をJavaの標準出力に表示する

リダイレクト種別をINHERITに設定することにより、親プロセス(java)の標準出力/エラー出力を引き継ぐことで、ping実行結果をjavaの標準出力に書き込むこともできる。

ProcessBuilder pb = new ProcessBuilder("ping", "-c", "3", "127.0.0.1");

// 子プロセスの標準入出力・標準エラー出力をjavaと同じものに設定する
pb.inheritIO();

try {
  Process p = pb.start();
  p.waitFor();
  System.out.println(pb.redirectInput());
} catch (IOException | InterruptedException e) {
  // 例外ハンドリング処理
}

ProcessBuilder.inheritIO()の実行することで、書き込み先ストリームは親プロセスであるJavaと同じストリームになる。結果は以下のように表示される。

PING 127.0.0.1 (127.0.0.1): 56 data bytes
64 bytes from 127.0.0.1: icmp_seq=0 ttl=64 time=0.055 ms
64 bytes from 127.0.0.1: icmp_seq=1 ttl=64 time=0.049 ms
64 bytes from 127.0.0.1: icmp_seq=2 ttl=64 time=0.045 ms

--- 127.0.0.1 ping statistics ---
3 packets transmitted, 3 packets received, 0.0% packet loss
round-trip min/avg/max/stddev = 0.045/0.050/0.055/0.004 ms
INHERIT

PIPEを表示させた最初の例と異なり、ping結果の後にINHERITが表示されている。これはp.waitFor()でping終了を待つ間に、javaと同じ標準出力にpingの実行結果が出力されたことを示す。ProcessBuilder.redirectInput()から返されるINHERITはまた、子プロセスの標準入出力ストリームが親から引き継がれている状態であることを示す。

まとめ

  • 結果をストリーム経由で取得したい場合は、ProcessBuilder.getInputStream()
  • 結果をファイルに書き込みたい場合は、ProcessBuilder.redirectOutput(File file)
  • 結果をjavaの標準出力に表示したい場合は、ProcessBuilder.inheritIO()

詳細については、java.lang.ProcessBuilderクラスのJavaDocを参考にしてください。