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
クラスを利用して外部プロセスを実行してみました