Java8以上の世の中だと思いますので、外部プロセスを実行する場合はProcessBuilder
クラスを使いましょう。
今回は、外部プロセスが出力する標準出力や標準エラー出力の内容は無視して、終了コードだけを取得する例となっています。
Javaで?外部プロセスを実行する場合、よく出る話ですが以下の考慮が必要です。
外部プロセスは標準出力(や標準エラー)に書き込みたいしたいのに、受け側のProcessのInputStreamがいっぱいになってしまいます。 そのため、ストリームから読み出してやらないとバッファーが不足して、書き込み側(外部プロセス)がブロッキング(一時停止)してしまい、 そのプロセスは終了しないことになります。
実行用クラスは以下のような感じになるかと思います。
import java.io.IOException; import java.io.InputStream; import java.io.UncheckedIOException; import java.util.Arrays; import java.util.LinkedList; import java.util.List; import java.util.concurrent.TimeUnit; public final class CommandExecutor { public static int execute(String cmd, List<String> params, long timeoutSec) { List<String> cmdAndParams = new LinkedList<>(); cmdAndParams.add(cmd); cmdAndParams.addAll(params); return execute(cmdAndParams, timeoutSec); } public static int execute(String cmd, long timeoutSec) { return execute(Arrays.asList(cmd), timeoutSec); } private static int execute(List<String> cmdAndParams, long timeoutSec) { ProcessBuilder builder = new ProcessBuilder(cmdAndParams); builder.redirectErrorStream(true); // 標準エラー出力の内容を標準出力にマージする Process process; try { process = builder.start(); } catch (IOException e) { throw new CommandExecuteFailedException("Command launch failed. [cmd: " + cmdAndParams + "]", e); } int exitCode; try { // 標準出力をすべて読み込む new Thread(() -> { try (InputStream is = process.getInputStream()) { while (is.read() >= 0); } catch (IOException e) { throw new UncheckedIOException(e); } }).start(); boolean end = process.waitFor(timeoutSec, TimeUnit.SECONDS); if (end) { exitCode = process.exitValue(); } else { throw new CommandExecuteFailedException("Command timeout. [CommandPath: " + cmdAndParams + "]"); } } catch (InterruptedException e) { throw new CommandExecuteFailedException("Command interrupted. [CommandPath: " + cmdAndParams + "]", e); } finally { if (process.isAlive()) { process.destroy(); // プロセスを強制終了 } } return exitCode; } private CommandExecutor() { } }
独自の例外クラスも一応定義
public final class CommandExecuteFailedException extends RuntimeException { public CommandExecuteFailedException(String message) { super(message); } public CommandExecuteFailedException(String message, Throwable cause) { super(message, cause); } }
実行例は以下の通りです
存在するコマンドの実行
java -version
■実行用コード
import java.util.Arrays; public class Main { public static void main(String[] args) { System.out.println("Command start [java -version]"); int exitCode = CommandExecutor.execute("java", Arrays.asList("-version"), 3); System.out.printf("Command end. [exitCode: %d]\n", exitCode); } }
■実行結果
Command start [java -version] Command end. [exitCode: 0]
正常終了しています
存在しないオプションを指定して実行
java -unknown
■実行用コード
(上記と同様でmainで実行していますが、同様の箇所のコードをかなり省略しています)
System.out.println("Command start [java -unknown]"); int exitCode = CommandExecutor.execute("java", Arrays.asList("-unknown"), 3); System.out.printf("Command end. [exitCode: %d]\n", exitCode);
■実行結果
Command start [java -unknown] Command end. [exitCode: 1]
exitCode = 1 で異常終了しています
タイムアウトするコマンドを実行
ping 127.0.0.1 -c 100
■実行用コード
タイムアウト時間を3秒にして実行しています
System.out.println("Command start [ping 127.0.0.1 -c 100]"); int exitCode = CommandExecutor.execute("ping", Arrays.asList("127.0.0.1", "-c", "100"), 3); System.out.printf("Command end. [exitCode: %d]\n", exitCode);
■実行結果
Command start [ping 127.0.0.1 -c 100] Exception in thread "main" net.yyuki.cmd.CommandExecuteFailedException: Command timeout. [CommandPath: [ping, 127.0.0.1, -c, 100]] at net.yyuki.cmd.CommandExecutor.execute(CommandExecutor.java:51) at net.yyuki.cmd.CommandExecutor.execute(CommandExecutor.java:18) at net.yyuki.cmd.trial.Main3.main(Main3.java:10)
タイムアウトして、CommandExecuteFailedException
がスローされています
存在しないマンドを実行
unknown_cmd -h
■実行用コード
存在しないunknown_cmd
というコマンドを実行しています
System.out.println("Command start [unknown_cmd -h]"); int exitCode = CommandExecutor.execute("unknown_cmd", Arrays.asList("-h"), 3); System.out.printf("Command end. [exitCode: %d]\n", exitCode);
■実行結果
Command start [unknown_cmd -h] Exception in thread "main" net.yyuki.cmd.CommandExecuteFailedException: Command launch failed. [cmd: [unknown_cmd, -h]] at net.yyuki.cmd.CommandExecutor.execute(CommandExecutor.java:33) at net.yyuki.cmd.CommandExecutor.execute(CommandExecutor.java:18) at net.yyuki.cmd.trial.Main5.main(Main5.java:10) Caused by: java.io.IOException: Cannot run program "unknown_cmd": error=2, No such file or directory at java.lang.ProcessBuilder.start(ProcessBuilder.java:1048) at net.yyuki.cmd.CommandExecutor.execute(CommandExecutor.java:31) ... 2 more Caused by: java.io.IOException: error=2, No such file or directory at java.lang.UNIXProcess.forkAndExec(Native Method) at java.lang.UNIXProcess.<init>(UNIXProcess.java:247) at java.lang.ProcessImpl.start(ProcessImpl.java:134) at java.lang.ProcessBuilder.start(ProcessBuilder.java:1029) ... 3 more
対象のコマンドが存在せず、CommandExecuteFailedException
がスローされています
まとめ
ProcessBuilder
クラスを利用して外部プロセスを実行してみました