【Java】gzipやwgetなどの外部プロセスを実行する方法

Agenda
更新日時

Javaで外部プロセスを実行する方法についてご紹介致します。

Javaで例えばgzipコマンドやwget、curlコマンド、nkfコマンド等の外部プロセスを実行したい場合、方法としてはProcessBuilderとProcessを利用します。

Java1.4以前はRuntimeを利用しておりましたが、1.5以降からはRuntimeではなくProcessBuilderが推奨されます。

外部プロセスを実行する場合、外部プロセスの標準出力、標準エラーはProcessのInputStreamが受け手になります。

そのため、注意点としましては、InputStreamを読み込まずにいると、外部プロセスの標準出力、標準エラーがバッファサイズを超えてしまう可能性があり、そのような状況になると外部プロセスは標準出力への出力ができず、待ち状態となってしまいます。

さらにJavaプログラム上で外部プロセスの終了を待つようにしていると、外部プロセスはバッファが空くのを待っており、Javaプログラム側はプロセス終了を待っているというお見合い(デッドロック)状態となってしまう可能性があります。

従って、外部プロセスを実行する場合は標準出力と標準エラーを読み込むようにするか、ProcessのInputStreamではなくファイルへ出力するなど標準出力、標準エラーへの出力が待ち状態とならないように工夫する必要があります。

 


Javaで外部プロセスを実行するサンプル(1)


外部プロセスを実行するサンプルコードです。

標準出力、標準エラーをJavaと同じ標準出力、標準エラーに出力する場合は以下のようになります。

外部プロセスの実行内容がJavaのログに残るため、外部プロセスの実行内容を取得する必要が無い場合はこちらが良いかもしれません。

public static void main(String[] args) {
    java.io.File dir = new File("/tmp");
    String[] Command = {"tar","cvzf", "test.tar.gz", "test"};

    long timeOutSec = 5 * 60; // 5分でタイムアウト

    boolean isFinished = processDone(Command, dir, timeOutSec);

}


public static boolean processDone(String[] Command, File dir, long timeOutSec) {
    List<String> cmdArray = Arrays.asList(Command);

    ProcessBuilder builder = new ProcessBuilder(cmdArray);
    // 作業ディレクトリ
    builder.directory(dir);
    // 標準エラーを標準出力へ統合
    builder.redirectErrorStream(true);
    // Javaと同じ標準出力に統合
    builder.inheritIO();

    Process p = null;
    boolean isFinished = false;

    try {
        p = builder.start();

        isFinished = p.waitFor(timeOutSec, TimeUnit.SECONDS); // プロセスが正常終了するまで待機

    } catch (InterruptedException e) {
        System.out.println(e.getMessage());
    } catch (IOException e) {
        System.out.println(e.getMessage());
    } finally {
        if (Objects.nonNull(p) && p.isAlive()) {
            p.destroy();
        }
    }
    return isFinished;
}

 


Javaで外部プロセスを実行するサンプル(2)


外部プロセスを実行するサンプルコードです。

標準出力、標準エラーをファイルに出力する場合は以下のようになります。

外部プロセスの実行内容がファイルに残るため、外部プロセスの実行内容を取得する場合やJavaのログとは分離して管理したい場合はこちらが良いと思います。

public static void main(String[] args) {
    java.io.File dir = new File("/tmp");
    String[] Command = {"tar","cvzf", "test.tar.gz", "test"};

    long timeOutSec = 5 * 60; // 5分でタイムアウト

    boolean isFinished = processDone(Command, dir, timeOutSec);

}


public static boolean processDone(String[] Command, File dir, long timeOutSec) {
    List<String> cmdArray = Arrays.asList(Command);

    ProcessBuilder builder = new ProcessBuilder(cmdArray);
    // 作業ディレクトリ
    builder.directory(dir);
    // 標準エラーを標準出力へ統合
    builder.redirectErrorStream(true);
    // logファイルに出力
    File log = new File("command.log");
    builder.redirectOutput(log);
    // builder.redirectOutput(Redirect.appendTo(log)); // ファイルへ追記の場合

    Process p = null;
    boolean isFinished = false;

    try {
        p = builder.start();

        isFinished = p.waitFor(timeOutSec, TimeUnit.SECONDS); // プロセスが正常終了するまで待機

    } catch (InterruptedException e) {
        System.out.println(e.getMessage());
    } catch (IOException e) {
        System.out.println(e.getMessage());
    } finally {
        if (Objects.nonNull(p) && p.isAlive()) {
            p.destroy();
        }
    }
    return isFinished;
}

 


Javaで外部プロセスを実行するサンプル(3)


外部プロセスを実行するサンプルコードです。

以下のサンプルは標準出力と標準エラーの出力先をProcessのInputStreamに設定し、読み込み内容を破棄しています。

外部プロセスが実行できればよく、出力内容は全く不要の場合はこちらでも良いと思います。

しかしながら、出力内容が不要な場合、上記(1)、(2)のいずれかでも問題無く、わざわざ読み込んで破棄する方法を選択する必要は無いと思います。外部プロセスの出力結果をJavaプログラム上で利用したい場合も直接Streamを読み込むより、ファイルに出力した内容を読み込むほうが外部プロセスの結果が残るので良いと思います。

public static void main(String[] args) {
    java.io.File dir = new File("/tmp");
    String[] Command = {"tar","cvzf", "test.tar.gz", "test"};

    long timeOutSec = 5 * 60; // 5分でタイムアウト

    boolean isFinished = processDone(Command, dir, timeOutSec);

}


public static boolean processDone(String[] Command, File dir, long timeOutSec) {
    List<String> cmdArray = Arrays.asList(Command);

    ProcessBuilder builder = new ProcessBuilder(cmdArray);
    // 作業ディレクトリ
    builder.directory(dir);
    // 標準エラーを標準出力へ統合
    builder.redirectErrorStream(true);

    boolean isFinished = false;
    try {
        Process p = builder.start();

        try {
            new Thread(() -> {
                try (InputStream is = p.getInputStream()) {
                    // 読み込むだけで何もしない
                    while (is.read() >= 0);
                } catch (IOException e) {
                    throw new UncheckedIOException(e);
                }
            } ).start();

            isFinished = p.waitFor(timeOutSec, TimeUnit.SECONDS); // プロセスが正常終了するまで待機

        } catch (InterruptedException e) {
            System.out.println(e.getMessage());
        } finally {
            if (p.isAlive()) {
                p.destroy();
            }
        }
    } catch (IOException e) {
        System.out.println(e.getMessage());
    }
    return isFinished;
}

 


まとめ


Javaで外部プロセスを実行する場合はProcessBuilderを利用します。

外部プロセスの標準出力、標準エラーの出力先をJavaの標準出力へ統合するか、個別のlogファイルに出力するようにして、外部プロセスが標準出力、標準エラーを出力できるようにします。

ProcessBuilder builder = new ProcessBuilder(cmdArray);

// まずは標準エラーを標準出力へ統合する
builder.redirectErrorStream(true);

// ファイルへ出力する場合はログファイルを指定する
File log = new File("command.log");
builder.redirectOutput(log);

// Javaの標準出力へ統合する場合はそのように宣言する
builder.inheritIO();

 

以上

カテゴリ

コメントを追加

この質問はあなたが人間の訪問者であるかどうかをテストし、自動化されたスパム送信を防ぐためのものです。