「Apache Commons Exec」でJavaから外部プログラムを起動/制御する

Javaバッチなどで、どうしても外部のプログラムをコマンドラインから起動し、その結果を受け取りたい場合があります。

こんな時、標準ライブラリに用意されているjava.lang.ProcessBuilderクラスや、java.lang.Runtime.exec()メソッドを利用するのが一般的ですが、あまり使い勝手が良いとはいえません。

そこで、「Apache Commons Exec」を利用して、もう少しフレキシブルに記述してみたいと思います。

最初に利用するライブラリを「Apache Commons Exec」のページからダウンロードします。
私がダウンロードしたときは、「commons-exec-1.1-bin.zip」でした。

ダウンロードしたファイルを解凍して、クラスパスに「commons-exec-1.1.jar」を追加しましょう。

一番シンプルなプログラム実行

さて、早速、一番簡単なコマンドラインの実行のプログラムを書いてみます。

今回は、「ipconfig /all」を実行させてみましょう。

import org.apache.commons.exec.CommandLine;
import org.apache.commons.exec.DefaultExecutor;
import org.apache.commons.exec.ExecuteException;
 
 
public class TestBatch {
 
public static void main(String[] arg) throws Exception {
 
String line = "ipconfig /all";
CommandLine cmdLine = CommandLine.parse(line);
 
    //Executorを作成
    DefaultExecutor executor = new DefaultExecutor();
    try {
      executor.setExitValue(0);
      //execute
      int exitValue = executor.execute(cmdLine);
      System.out.println("return=" + exitValue);
 
    } catch (ExecuteException ex) {
      ex.printStackTrace();
    }
 
}
 
}

コマンドの実行には「CommandLine」を生成します。

また、このクラスのpaese()メソッドにコマンドラインの文字列を渡すことで、コマンドがセットされます。

あとは、DefaultExecutor.execute()を実行することで、コマンドが実行されます。

このメソッドの戻り値は、実行プログラムの結果となります。

また、executor.setExitValue(0)では、どの値がプログラムの実行結果として返ってきた場合に正常終了とみなすかをセットします。

つまり、上記の例では、戻り値が0以外の場合には異常終了とみなされて、「org.apache.commons.exec.ExecuteException」がスローされるというわけです。

タイムアウトの設定

プロセスの実行にはタイムアウトを設定することもできます。

特に、処理時間が長いプログラムなどで、あまりに実行結果が返ってこない場合は、途中で処理を止めることもあり得ます。

そんな時に利用できるのが、「ExecuteWatchdog 」クラスです。起動したプログラムの状態を監視し、設定したタイムアウト時間が過ぎると「ExecuteException」をスローし、プロセスを中断します。

String line = "notepad test.txt";
CommandLine cmdLine = CommandLine.parse(line);
 
 
//Executorを作成
DefaultExecutor executor = new DefaultExecutor();
try {
  executor.setExitValue(0);
  //タイムアウト時間のセット
  ExecuteWatchdog watchdog = new ExecuteWatchdog(10000);
  executor.setWatchdog(watchdog);
  //execute
  int exitValue = executor.execute(cmdLine);
  System.out.println("return=" + exitValue);
 
} catch (ExecuteException ex) {
  ex.printStackTrace();
}

非同期処理

非常に長い時間かかる処理の場合には、非同期で処理を実行したいこともあるでしょう。

そんな時に利用できるのが、「DefaultExecuteResultHandler」です。

String line = "notepad test.txt";
CommandLine cmdLine = CommandLine.parse(line);
 
DefaultExecuteResultHandler resultHandler = new DefaultExecuteResultHandler();
 
//Executorを作成
DefaultExecutor executor = new DefaultExecutor();
try {
  executor.setExitValue(0);
  ExecuteWatchdog watchdog = new ExecuteWatchdog(10000);
  executor.setWatchdog(watchdog);
  //execute
  executor.execute(cmdLine, resultHandler);
  System.out.println("executed!");
 
  resultHandler.waitFor();
 
} catch (ExecuteException ex) {
  ex.printStackTrace();
}

executor.execute(cmdLine, resultHandler) のように、非同期処理をハンドリングするクラスのインスタンスとともに実行します。

すると、標準出力に「」がすぐに表示され、処理が非同期で実行されたことが分かると思います。

その後、処理結果を取得したいところで、
resultHandler.waitFor();
を記述しておけば、処理が終わるまで待つことになります。

標準出力の結果をファイルに出力する

String line = "ping /n 5 127.0.0.1";
CommandLine cmdLine = CommandLine.parse(line);
 
DefaultExecuteResultHandler resultHandler = new DefaultExecuteResultHandler();
 
//Executorを作成
DefaultExecutor executor = new DefaultExecutor();
try {
  PumpStreamHandler streamHandler =
    new PumpStreamHandler(new FileOutputStream("c:result.txt"));
  executor.setStreamHandler(streamHandler);
 
  executor.setExitValue(0);
  ExecuteWatchdog watchdog = new ExecuteWatchdog(30000);
  executor.setWatchdog(watchdog);
  //execute
  executor.execute(cmdLine, resultHandler);
  System.out.println("executed!");
 
  resultHandler.waitFor();
 
} catch (ExecuteException ex) {
  ex.printStackTrace();
}

上記のようにすると、標準出力に表示される実行結果をファイルに出力することができます。

PumpStreamHandlerを利用します。

結果をそのままOutputStreamで受け取る方法も試したのですが、いろいろとめんどうな処理を記述しなければならず、結局、ファイルに吐きだして、それを読み込んだ方が簡単で確実ではないかと感じました。

また、環境変数をセットするには、

EnvironmentUtils.addVariableToEnvironment( env, "TESTPATH=aaaa" );
executor.execute(cmdLine, env, resultHandler);

が利用できます。