サーチ…


備考

APIでは、バージョン1.5以降、 ProcessBuilder.start()を使用してプロセスを作成することをお勧めします。

もう一つの重要なwaitForは、 waitForによって生成された終了値が実行されているプログラム/スクリプトに依存していることです。たとえば、 calc.exeによって生成される終了コードはnotepad.exeとは異なります

簡単な例(Javaバージョン<1.5)

この例では、ウィンドウの電卓を呼び出します。終了コードは、呼び出されるプログラム/スクリプトに応じて変化することに注意することが重要です。

package process.example;

import java.io.IOException;

public class App {

    public static void main(String[] args) {
        try {
            // Executes windows calculator
            Process p = Runtime.getRuntime().exec("calc.exe");

            // Wait for process until it terminates
            int exitCode = p.waitFor();

            System.out.println(exitCode);
        } catch (IOException e) {
            e.printStackTrace();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}

ProcessBuilderクラスの使用

ProcessBuilderクラスを使用すると、コマンドラインから簡単にコマンドを送信できます。必要なのは、入力するコマンドを構成する文字列の一覧です。コマンドを実行するには、単にProcessBuilderインスタンスでstart()メソッドを呼び出します。

Add.exeという2つの引数をとり、それらを追加するプログラムがある場合、コードは次のようになります。

List<String> cmds = new ArrayList<>();
cmds.add("Add.exe"); //the name of the application to be run
cmds.add("1"); //the first argument
cmds.add("5"); //the second argument

ProcessBuilder pb = new ProcessBuilder(cmds);

//Set the working directory of the ProcessBuilder so it can find the .exe
//Alternatively you can just pass in the absolute file path of the .exe
File myWorkingDirectory = new File(yourFilePathNameGoesHere);
pb.workingDirectory(myWorkingDirectory);

try {
    Process p = pb.start(); 
} catch (IOException e) {
    e.printStackTrace();
}

心に留めておくべきこと:

  • コマンドの配列はすべて文字列配列でなければなりません
  • コマンドラインでプログラムを呼び出した場合(つまり、.exeの名前が最初の引数の後に置かれない場合)、コマンドは配列の順序でなければなりません
  • 作業ディレクトリを設定するときは、ファイル名だけでなくFileオブジェクトを渡す必要があります

ブロッキングと非ブロッキングのコール

一般に、コマンドラインを呼び出すとき、プログラムはコマンドを送信してから実行を続行します。

しかし、呼び出されたプログラムが終了するのを待ってから、独自の実行を続けることができます(ex。呼び出されたプログラムはデータをファイルに書き出し、プログラムはそのデータにアクセスする必要があります)。

これは、返されたProcessインスタンスからwaitFor()メソッドを呼び出すことで簡単に実行できます。

使用例:

//code setting up the commands omitted for brevity...

ProcessBuilder pb = new ProcessBuilder(cmds);

try {
    Process p = pb.start();
    p.waitFor();
} catch (IOException e) {
    e.printStackTrack();
} catch (InterruptedException e) {
    e.printStackTrace();
}

//more lines of code here...

ch.vorburger.exec

raw java.lang.ProcessBuilder APIを直接使用してJavaから外部プロセスを起動するのは少し面倒です。 Apache Commons Execライブラリを使うと少し楽になります。 ch.vorburger.execライブラリは、さらにCommons Execを拡張して、本当に便利になりました。

 ManagedProcess proc = new ManagedProcessBuilder("path-to-your-executable-binary")
     .addArgument("arg1")
     .addArgument("arg2")
     .setWorkingDirectory(new File("/tmp"))
     .setDestroyOnShutdown(true)
     .setConsoleBufferMaxLines(7000)
     .build();

proc.start();
int status = proc.waitForExit();
int status = proc.waitForExitMaxMsOrDestroy(3000);
String output = proc.getConsole();

proc.startAndWaitForConsoleMessageMaxMs("started!", 7000);
// use service offered by external process...
proc.destroy();

落とし穴:Runtime.exec、Process、およびProcessBuilderはシェル構文を理解していません

Runtime.exec(String ...)Runtime.exec(String)メソッドを使用すると、外部プロセス1のようにコマンドを実行することができます。最初のバージョンでは、コマンド名とコマンド引数を文字列配列の別々の要素として指定し、JavaランタイムはOSランタイムシステムに外部コマンドを開始するよう要求します。 2番目のバージョンは使い方が簡単ですが、いくつかの落とし穴があります。

まず、安全に使用されているexec(String)使用例を次に示します。

Process p = Runtime.exec("mkdir /tmp/testDir");
p.waitFor();
if (p.exitValue() == 0) {
    System.out.println("created the directory");
}

パス名のスペース

上記の例を一般化して、任意のディレクトリを作成できるとします。

Process p = Runtime.exec("mkdir " + dirPath);
// ...

これは通常動作しますが、 dirPathが "/ home / user / My Documents"などの場合は失敗します。問題は、 exec(String)が空白を探すだけで文字列をコマンドと引数に分割することです。コマンド文字列:

"mkdir /home/user/My Documents"

分割されます:

"mkdir", "/home/user/My", "Documents"

これは2つではなく1つの引数を必要とするため、 "mkdir"コマンドが失敗する原因となります。

これに直面して、一部のプログラマはパス名の前後に引用符を追加しようとします。これはどちらも動作しません:

"mkdir \"/home/user/My Documents\""

分割されます:

"mkdir", "\"/home/user/My", "Documents\""

スペースを "引用"しようとして追加された余分な二重引用符は、他の空白以外の文字と同様に扱われます。確かに、私たちがスペースを引用したりエスケープしたりするものはすべて失敗するでしょう。

この特定の問題に対処する方法は、 exec(String ...)オーバーロードを使用することです。

Process p = Runtime.exec("mkdir", dirPath);
// ...

これは、 dirpathに空白文字が含まれていると動作します。これは、 execこのオーバーロードが引数を分割しようとしないためです。文字列はそのままOS execシステムコールに渡されます。

リダイレクト、パイプライン、その他のシェル構文

外部コマンドの入力または出力をリダイレクトする、またはパイプラインを実行するとします。例えば:

Process p = Runtime.exec("find / -name *.java -print 2>/dev/null");

または

Process p = Runtime.exec("find source -name *.java | xargs grep package");

最初の例は、ファイルシステム内のすべてのJavaファイルの名前をリストし、2番目の例は、 package2を「ソース」ツリーのJavaファイルに出力しpackage

これらは期待通りに動作しません。最初のケースでは、 "find"コマンドは "2 / dev / null"をコマンド引数として実行されます。これはリダイレクトとして解釈されません。 2番目の例では、パイプ文字( "|")とそれに続く作業が "find"コマンドに与えられます。

ここでの問題は、 execメソッドとProcessBuilderがシェルの構文を理解していないことです。これには、リダイレクション、パイプライン、変数展開、グロビングなどが含まれます。

いくつかのケース(単純なリダイレクションなど)では、 ProcessBuilderを使用して簡単に目的の効果を達成できます。しかし、これは一般的ではありません。別のアプローチは、シェルでコマンドラインを実行することです。例えば:

Process p = Runtime.exec("bash", "-c", 
                         "find / -name *.java -print 2>/dev/null");

または

Process p = Runtime.exec("bash", "-c", 
                         "find source -name \\*.java | xargs grep package");

しかし、2番目の例では、ワイルドカードをシェルではなく "find"によって解釈させたいので、ワイルドカード文字( "*")をエスケープする必要があることに注意してください。

シェル組み込みコマンドが機能しない

次の例は、UNIXのようなシェルを持つシステムでは動作しないものとします。

Process p = Runtime.exec("cd", "/tmp");     // Change java app's home directory

または

Process p = Runtime.exec("export", "NAME=value");  // Export NAME to the java app's environment

これがうまくいかない理由はいくつかあります:

  1. "cd"と "export"コマンドはシェルの組み込みコマンドです。別個の実行可能ファイルとして存在しません。

  2. シェルビルトインが行うべきこと(例えば、作業ディレクトリの変更、環境の更新)を行うためには、その状態が存在する場所を変更する必要があります。通常のアプリケーション(Javaアプリケーションを含む)の場合、状態はアプリケーションプロセスに関連付けられます。たとえば、 "cd"コマンドを実行する子プロセスは、親 "java"プロセスの作業ディレクトリを変更できませんでした。同様に、1つのexecプロセスは、それに続くプロセスの作業ディレクトリを変更することはできません。

この推論はすべてのシェル組み込みコマンドに適用されます。


1 - ProcessBuilderも使用できますが、これはこの例のポイントには関係ありません。

2 - これはちょっとラフで準備ができていますが、このアプローチの失敗はこの例には関係ありません。



Modified text is an extract of the original Stack Overflow Documentation
ライセンスを受けた CC BY-SA 3.0
所属していない Stack Overflow