技術とか戦略とか

IT技術者が技術や戦略について書くブログです。

java:javaからのOSコマンド呼び出しと注意点

javaのプログラムからOSのコマンドを実行したい場合は、Runtimeクラスのexecメソッドで実現できます。
 
しかし、OSのコマンドは別プロセスで立ち上がるので注意が必要です。
ProcessクラスのwaitForメソッドでプロセスの終了を待たないと、処理が前後してしまい意図しない挙動となることがあります。
また、destroyメソッドでプロセスを明確に終了させ、資源を回収することも重要です。
 
以下は、プロセスの終了を待たないと意図しない挙動となることを確認するためのテストコードです。
javaのプログラムでは、OSのコマンドを使用して、3秒のスリープ後に hoge.txt を aフォルダ から bフォルダ へ移動させています。
waitForメソッドを使用しなかった場合、javaのプログラムが終了した時点で hoge.txt の bフォルダ への移動が完了していない(javaのプログラムが立ちあげたプロセスが終了していない)ことを確認できます。
逆に、waitForメソッドを使用した場合は、javaのプログラムが終了した時点で hoge.txt の bフォルダ への移動が完了している(javaのプログラムが立ちあげたプロセスが終了している)ことを確認できます。
 
【テストコード】
・RuntimeTest.java
public class RuntimeTest {
    public static void main(String args[]){
        try {
            Runtime runtime = Runtime.getRuntime();
            Process p = runtime.exec
                ("cmd /c timeout /t 3 > nul & move a\\hoge.txt b\\");
            p.waitFor(); // プロセス終了を待つ
            p.destroy(); // プロセスを明確に終了させ、資源を回収
        } catch (Exception e) {
            System.out.println(e);
        }
    }
}
 
【実行用バッチ】
・RuntimeTest.bat
rem javaソースのコンパイル
javac RuntimeTest.java

rem モジュール実行前の確認
dir a
dir b

rem モジュール実行
java RuntimeTest

rem モジュール実行後の確認
dir a
dir b

rem モジュール削除
del RuntimeTest.class

rem 処理終了
pause
exit
 
【「p.waitFor();」と「p.destroy();」をコメントアウトした場合の実行結果】
C:\tmp>rem javaソースのコンパイル

C:\tmp>javac RuntimeTest.java

C:\tmp>rem モジュール実行前の確認

C:\tmp>dir a
 ドライブ C のボリューム ラベルは  です
 ボリューム シリアル番号は  です

 C:\tmp\a のディレクト

2019/03/07  22:37    <DIR>          .
2019/03/07  22:37    <DIR>          ..
2019/03/07  22:23                 6 hoge.txt
               1 個のファイル                   6 バイト
               2 個のディレクトリ  19,641,397,248 バイトの空き領域

C:\tmp>dir b
 ドライブ C のボリューム ラベルは  です
 ボリューム シリアル番号は  です

 C:\tmp\b のディレクト

2019/03/07  22:37    <DIR>          .
2019/03/07  22:37    <DIR>          ..
               0 個のファイル                   0 バイト
               2 個のディレクトリ  19,641,397,248 バイトの空き領域

C:\tmp>rem モジュール実行

C:\tmp>java RuntimeTest

C:\tmp>rem モジュール実行後の確認

C:\tmp>dir a
 ドライブ C のボリューム ラベルは  です
 ボリューム シリアル番号は  です

 C:\tmp\a のディレクト

2019/03/07  22:37    <DIR>          .
2019/03/07  22:37    <DIR>          ..
2019/03/07  22:23                 6 hoge.txt
               1 個のファイル                   6 バイト
               2 個のディレクトリ  19,641,401,344 バイトの空き領域

C:\tmp>dir b
 ドライブ C のボリューム ラベルは  です
 ボリューム シリアル番号は  です

 C:\tmp\b のディレクト

2019/03/07  22:37    <DIR>          .
2019/03/07  22:37    <DIR>          ..
               0 個のファイル                   0 バイト
               2 個のディレクトリ  19,641,401,344 バイトの空き領域

C:\tmp>rem モジュール削除

C:\tmp>del RuntimeTest.class

C:\tmp>rem 処理終了

C:\tmp>pause
続行するには何かキーを押してください . . .
 
【「p.waitFor();」と「p.destroy();」を有効にした場合の実行結果】
C:\tmp>rem javaソースのコンパイル

C:\tmp>javac RuntimeTest.java

C:\tmp>rem モジュール実行前の確認

C:\tmp>dir a
 ドライブ C のボリューム ラベルは  です
 ボリューム シリアル番号は  です

 C:\tmp\a のディレクト

2019/03/07  22:43    <DIR>          .
2019/03/07  22:43    <DIR>          ..
2019/03/07  22:23                 6 hoge.txt
               1 個のファイル                   6 バイト
               2 個のディレクトリ  19,639,898,112 バイトの空き領域

C:\tmp>dir b
 ドライブ C のボリューム ラベルは  です
 ボリューム シリアル番号は  です

 C:\tmp\b のディレクト

2019/03/07  22:43    <DIR>          .
2019/03/07  22:43    <DIR>          ..
               0 個のファイル                   0 バイト
               2 個のディレクトリ  19,639,898,112 バイトの空き領域

C:\tmp>rem モジュール実行

C:\tmp>java RuntimeTest

C:\tmp>rem モジュール実行後の確認

C:\tmp>dir a
 ドライブ C のボリューム ラベルは  です
 ボリューム シリアル番号は  です

 C:\tmp\a のディレクト

2019/03/07  22:43    <DIR>          .
2019/03/07  22:43    <DIR>          ..
               0 個のファイル                   0 バイト
               2 個のディレクトリ  19,639,894,016 バイトの空き領域

C:\tmp>dir b
 ドライブ C のボリューム ラベルは  です
 ボリューム シリアル番号は  です

 C:\tmp\b のディレクト

2019/03/07  22:43    <DIR>          .
2019/03/07  22:43    <DIR>          ..
2019/03/07  22:23                 6 hoge.txt
               1 個のファイル                   6 バイト
               2 個のディレクトリ  19,639,894,016 バイトの空き領域

C:\tmp>rem モジュール削除

C:\tmp>del RuntimeTest.class

C:\tmp>rem 処理終了

C:\tmp>pause
続行するには何かキーを押してください . . .