Engineering Note

プログラミングなどの技術的なメモ

Javaからプロセスを実行する際に処理途中で停止する場合の対処

f:id:s3cr3t:20191130224053p:plain

Javaからシェルのコマンドなど実行する場合、RuntimeクラスやProcessBuilderクラスからコマンド名と引数を渡してあげることで、サブプロセスを作成することができます。

しかし、適切に処理を記載しなかった場合、プログラムが途中で停止してしまうことがあります。

今回はその際の対処方法について記載していきます。

  

 

RuntimeクラスとProcessBuilderクラスの違い

JavaではRuntimeクラスとProcessBuilderクラスを利用することで外部コマンドを実行することができます。

Runtimeクラスはexec()を実行する際に、内部でProcessBuilderを実行していて、

違いとしては、Runtimeクラス内部ではコマンドと複数のオプションを一つのStringクラスとして渡しても、内部でパースをして実行してくれます。

 

プロセスが停止する可能性のあるコード

まず停止する可能性のあるコードについて見ていきます。

 

 // 問題のあるコード
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;

public class TestMain {
    public static void main(String[] args) {
        List<String> cmd = new ArrayList<>();
        cmd.add("something_command");
        cmd.add("something_option");

        ProcessBuilder builder = new ProcessBuilder(cmd);
        builder.redirectErrorStream(true);  // 標準エラー出力の内容を標準出力にマージする

        try {
            Process process = builder.start();  // コマンド実行
            int exitStatus = process.waitFor();  // プロセス終了待ち
            System.out.println("exit status = " + exitStatus);  // 終了ステータスの表示
        } catch (InterruptedException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

 

上記でも問題なくコマンドを実行することができますが、実行したコマンドから大量に標準出力や標準エラー出力があった場合、入力バッファに余裕がなくなりプロセスが途中で停止してしまう恐れがあります。

 

プロセスが停止しないようにコーディングする 

プロセスが停止しないようにするためには、Processクラスのインスタンスの入力ストリームのバッファが一杯にならないようにしてあげる必要があります。

 

 // 修正したコード
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.util.ArrayList;
import java.util.List;

public class TestMain {
    public static void main(String[] args) {
        List<String> cmd = new ArrayList<>();
        cmd.add("something_command");
        cmd.add("something_option");

        ProcessBuilder builder = new ProcessBuilder(cmd);
        builder.redirectErrorStream(true);  // 標準エラー出力の内容を標準出力にマージする

        String str;
        try {
            Process process = builder.start();  // コマンド実行
            try (BufferedReader br = new BufferedReader(new InputStreamReader(process.getInputStream()))) {
                while((str = br.readLine()) != null) {
                    System.out.println(str);
                }
            }
            int exitStatus = process.waitFor();  // プロセス終了待ち
            System.out.println("exit status = " + exitStatus);  // 終了ステータスの表示
        } catch (InterruptedException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

 

これでプロセスが途中で停止する心配はなくなりました。

 

参考書籍

Javaセキュアコーディングスタンダード CERT/ Oracle版