web-dev-qa-db-ja.com

ProcessBuilderとRuntime.exec()の違い

Javaコードから外部コマンドを実行しようとしていますが、Runtime.getRuntime().exec(...)new Process(...).start()には違いがあります。

Runtimeを使用する場合:

Process p = Runtime.getRuntime().exec(installation_path + 
                                       uninstall_path + 
                                       uninstall_command + 
                                       uninstall_arguments);
p.waitFor();

exitValueは0で、コマンドは正常に終了します。

ただし、ProcessBuilderの場合:

Process p = (new ProcessBuilder(installation_path +    
                                 uninstall_path +
                                 uninstall_command,
                                 uninstall_arguments)).start();
p.waitFor();

waitForは戻りますが、終了値は1001で、コマンドは途中で終了します。

ProcessBuilderの問題を解決するにはどうすればよいですか?

89
gal

Runtime.getRuntime().exec(...)のさまざまなオーバーロードは、文字列の配列または単一の文字列を受け取ります。 exec()の単一文字列のオーバーロードは、文字列配列を引数の配列にトークン化してから、文字列配列を受け取るexec()オーバーロードの1つに文字列配列を渡します。一方、ProcessBuilderコンストラクターは、文字列のvarargs配列または文字列のListのみを受け取ります。配列またはリスト内の各文字列は個別の引数であると想定されます。いずれにしても、取得された引数は、実行のためにOSに渡される文字列に結合されます。

たとえば、Windowsでは、

Runtime.getRuntime().exec("C:\DoStuff.exe -arg1 -arg2");

与えられた2つの引数でDoStuff.exeプログラムを実行します。この場合、コマンドラインはトークン化され、元に戻されます。しかしながら、

ProcessBuilder b = new ProcessBuilder("C:\DoStuff.exe -arg1 -arg2");

DoStuff.exe -arg1 -arg2C:\という名前のプログラムが存在しない限り、失敗します。これは、トークン化がないためです。実行するコマンドは既にトークン化されていると想定されます。代わりに、使用する必要があります

ProcessBuilder b = new ProcessBuilder("C:\DoStuff.exe", "-arg1", "-arg2");

または代わりに

List<String> params = Java.util.Arrays.asList("C:\DoStuff.exe", "-arg1", "-arg2");
ProcessBuilder b = new ProcessBuilder(params);
92
Luke Woodward

Runtime.getRuntime().exec()がStringコマンドをProcessBuilderに渡す方法を見てください。トークナイザーを使用してコマンドを個々のトークンに分解し、ProcessBuilderを構築するexec(String[] cmdarray, ......)を呼び出します。

単一の配列ではなく、文字列の配列を使用してProcessBuilderを作成すると、同じ結果が得られます。

ProcessBuilderコンストラクターはString... varargを受け取るため、コマンド全体を単一の文字列として渡すことは、ターミナルで引用符でコマンドを呼び出すのと同じ効果があります。

Shell$ "command with args"
17
Costi Ciudatu

はい、違いがあります。

したがって、ProcessBuilderに指示することは、名前にスペースやその他のジャンクが含まれる「コマンド」を実行することです。もちろん、オペレーティングシステムはその名前のコマンドを見つけることができず、コマンドの実行は失敗します。

13
Stephen C

ProcessBuilder.start()の実装は次のとおりであるため、Runtime.exec()Runtime.exec()の間に違いはありません。

public Process exec(String command) throws IOException {
    return exec(command, null, null);
}

public Process exec(String command, String[] envp, File dir)
    throws IOException {
    if (command.length() == 0)
        throw new IllegalArgumentException("Empty command");

    StringTokenizer st = new StringTokenizer(command);
    String[] cmdarray = new String[st.countTokens()];
    for (int i = 0; st.hasMoreTokens(); i++)
        cmdarray[i] = st.nextToken();
    return exec(cmdarray, envp, dir);
}

public Process exec(String[] cmdarray, String[] envp, File dir)
    throws IOException {
    return new ProcessBuilder(cmdarray)
        .environment(envp)
        .directory(dir)
        .start();
}

だからコード:

List<String> list = new ArrayList<>();
new StringTokenizer(command)
.asIterator()
.forEachRemaining(str -> list.add((String) str));
new ProcessBuilder(String[])list.toArray())
            .environment(envp)
            .directory(dir)
            .start();

以下と同じである必要があります:

Runtime.exec(command)

ありがとうdave_thompson_085コメント

11
Eugene Lopatkin