web-dev-qa-db-ja.com

process.waitFor()は決して戻りません

Process process = Runtime.getRuntime().exec("tasklist");
BufferedReader reader = 
    new BufferedReader(new InputStreamReader(process.getInputStream()));
process.waitFor();
89
user590444

waitFor()が返されない多くの理由があります。

しかし、通常は、実行されたコマンドが終了しないという事実に要約されます。

これには、多くの理由があります。

一般的な理由の1つは、プロセスが何らかの出力を生成し、適切なストリームから読み取らないことです。これは、バッファーがいっぱいになるとすぐにプロセスがブロックされ、プロセスが読み取りを続行するのを待つことを意味します。あなたのプロセスは順番に他のプロセスが終了するのを待ちます(それはあなたのプロセスを待っているからではありません...)。これは、古典的なデッドロック状態です。

ブロックしないように、プロセスの入力ストリームから継続的に読み取る必要があります。

Runtime.exec()のすべての落とし穴を説明し、 "Runtime.exec()will't" (はい、2000年からの記事ですが、コンテンツは引き続き適用されます!)

132
Joachim Sauer

出力が完了するのを待つ前に出力を読んでいないようです。これは、出力がバッファを満たさない場合にのみ問題ありません。存在する場合、出力をキャッチするまで待機します(catch-22)。

おそらくあなたが読んでいないいくつかのエラーがあります。これは、アプリケーションが停止し、waitForが永遠に待機する場合です。これを回避する簡単な方法は、エラーを通常の出力にリダイレクトすることです。

ProcessBuilder pb = new ProcessBuilder("tasklist");
pb.redirectErrorStream(true);
Process process = pb.start();
BufferedReader reader = new BufferedReader(new InputStreamReader(process.getInputStream()));
String line;
while ((line = reader.readLine()) != null)
    System.out.println("tasklist: " + line);
process.waitFor();
67
Peter Lawrey

Java docからも:

Java.lang

クラスプロセス

一部のネイティブプラットフォームは標準の入力および出力ストリームに限られたバッファサイズしか提供しないため、サブプロセスの入力ストリームの書き込みまたは出力ストリームの読み取りをすぐに失敗すると、サブプロセスがブロックされ、デッドロックさえ発生する場合があります。

Processから入力ストリーム(サブプロセスの出力ストリームにパイプする)のバッファーをクリアしないと、サブプロセスがブロックされる可能性があります。

これを試して:

Process process = Runtime.getRuntime().exec("tasklist");
BufferedReader reader =
new BufferedReader(new InputStreamReader(process.getInputStream()));
while ((reader.readLine()) != null) {}
process.waitFor();
38
RollingBoy

以前の回答に何か追加したいのですが、コメントする担当者がいないため、回答を追加します。これは、JavaでプログラミングしているAndroidユーザーを対象としています。

RollingBoyの投稿によると、このコードは私にとってはほとんど機能していました。

Process process = Runtime.getRuntime().exec("tasklist");
BufferedReader reader =
new BufferedReader(new InputStreamReader(process.getInputStream()));
while ((reader.readLine()) != null) {}
process.waitFor();

私の場合、戻り値なしでステートメントを実行していたため、waitFor()は解放されませんでした( "ip adddr flush eth0")。これを修正する簡単な方法は、常にステートメントで何かを返すようにすることです。私にとって、これは「ip adddr flush eth0 && echo done」を実行することを意味していました。終日バッファを読み取ることができますが、何も返されない場合、スレッドは待機を解放しません。

それが誰かを助けることを願っています!

10
Slopes

他の人が述べたように、stderrstdoutを消費する必要があります。

他の回答と比較すると、Java 1.7以降はさらに簡単です。 stderrおよびstdoutを読むために自分でスレッドを作成する必要はもうありません。 。

ProcessBuilder を使用し、メソッド redirectOutputredirectError または redirectErrorStream と組み合わせて使用​​します。

String directory = "/working/dir";
File out = new File(...); // File to write stdout to
File err = new File(...); // File to write stderr to
ProcessBuilder builder = new ProcessBuilder();
builder.directory(new File(directory));
builder.command(command);
builder.redirectOutput(out); // Redirect stdout to file
if(out == err) { 
  builder.redirectErrorStream(true); // Combine stderr into stdout
} else { 
  builder.redirectError(err); // Redirect stderr to file
}
Process process = builder.start();
5
Markus Weninger

いくつかの可能性があります。

  1. プロセスのstdoutのすべての出力を消費していません。
  2. プロセスのstderrのすべての出力を消費していません。
  3. プロセスはあなたからinputを待っていますが、あなたはそれを提供していないか、プロセスのstdinを閉じていません。
  4. プロセスはハードループで回転しています。
4
user207421

同じ理由で、inheritIO()を使用してJavaコンソールを外部アプリコンソールとマッピングすることもできます。

ProcessBuilder pb = new ProcessBuilder(appPath, arguments);

pb.directory(new File(appFile.getParent()));
pb.inheritIO();

Process process = pb.start();
int success = process.waitFor();
2
Vivek

同時に出力とエラーを消費してみてください

    private void runCMD(String CMD) throws IOException, InterruptedException {
    System.out.println("Standard output: " + CMD);
    Process process = Runtime.getRuntime().exec(CMD);

    // Get input streams
    BufferedReader stdInput = new BufferedReader(new InputStreamReader(process.getInputStream()));
    BufferedReader stdError = new BufferedReader(new InputStreamReader(process.getErrorStream()));
    String line = "";
    String newLineCharacter = System.getProperty("line.separator");

    boolean isOutReady = false;
    boolean isErrorReady = false;
    boolean isProcessAlive = false;

    boolean isErrorOut = true;
    boolean isErrorError = true;


    System.out.println("Read command ");
    while (process.isAlive()) {
        //Read the stdOut

        do {
            isOutReady = stdInput.ready();
            //System.out.println("OUT READY " + isOutReady);
            isErrorOut = true;
            isErrorError = true;

            if (isOutReady) {
                line = stdInput.readLine();
                isErrorOut = false;
                System.out.println("=====================================================================================" + line + newLineCharacter);
            }
            isErrorReady = stdError.ready();
            //System.out.println("ERROR READY " + isErrorReady);
            if (isErrorReady) {
                line = stdError.readLine();
                isErrorError = false;
                System.out.println("ERROR::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::" + line + newLineCharacter);

            }
            isProcessAlive = process.isAlive();
            //System.out.println("Process Alive " + isProcessAlive);
            if (!isProcessAlive) {
                System.out.println(":::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::: Process DIE " + line + newLineCharacter);
                line = null;
                isErrorError = false;
                process.waitFor(1000, TimeUnit.MILLISECONDS);
            }

        } while (line != null);

        //Nothing else to read, lets pause for a bit before trying again
        System.out.println("PROCESS WAIT FOR");
        process.waitFor(100, TimeUnit.MILLISECONDS);
    }
    System.out.println("Command finished");
}
1
Eduardo Reyes

私は同様の問題を観察したと思います:いくつかのプロセスが開始され、正常に実行されたように見えましたが、完了しませんでした。タスクマネージャーでプロセスを強制終了した場合を除き、関数waitFor()は永遠に待機していました。
ただし、コマンドラインの長さが127文字以下の場合は、すべて正常に機能しました。長いファイル名が避けられない場合は、環境変数を使用すると、コマンドライン文字列を短くすることができます。実際に実行するプログラムを呼び出す前に環境変数を設定するバッチファイルを(FileWriterを使用して)生成できます。このようなバッチの内容は次のようになります。

    set INPUTFILE="C:\Directory 0\Subdirectory 1\AnyFileName"
    set OUTPUTFILE="C:\Directory 2\Subdirectory 3\AnotherFileName"
    set MYPROG="C:\Directory 4\Subdirectory 5\ExecutableFileName.exe"
    %MYPROG% %INPUTFILE% %OUTPUTFILE%

最後のステップでは、ランタイムを使用してこのバッチファイルを実行します。

1

ここに私のために働く方法があります。注:このメソッドには、適用されない可能性のあるコードがあるため、無視してください。たとえば、「logStandardOut(...)、git-bashなど」。

private String exeShellCommand(String doCommand, String inDir, boolean ignoreErrors) {
logStandardOut("> %s", doCommand);

ProcessBuilder builder = new ProcessBuilder();
StringBuilder stdOut = new StringBuilder();
StringBuilder stdErr = new StringBuilder();

boolean isWindows = System.getProperty("os.name").toLowerCase().startsWith("windows");
if (isWindows) {
  String gitBashPathForWindows = "C:\\Program Files\\Git\\bin\\bash";
  builder.command(gitBashPathForWindows, "-c", doCommand);
} else {
  builder.command("bash", "-c", doCommand);
}

//Do we need to change dirs?
if (inDir != null) {
  builder.directory(new File(inDir));
}

//Execute it
Process process = null;
BufferedReader brStdOut;
BufferedReader brStdErr;
try {
  //Start the command line process
  process = builder.start();

  //This hangs on a large file
  // https://stackoverflow.com/questions/5483830/process-waitfor-never-returns
  //exitCode = process.waitFor();

  //This will have both StdIn and StdErr
  brStdOut = new BufferedReader(new InputStreamReader(process.getInputStream()));
  brStdErr = new BufferedReader(new InputStreamReader(process.getErrorStream()));

  //Get the process output
  String line = null;
  String newLineCharacter = System.getProperty("line.separator");

  while (process.isAlive()) {
    //Read the stdOut
    while ((line = brStdOut.readLine()) != null) {
      stdOut.append(line + newLineCharacter);
    }

    //Read the stdErr
    while ((line = brStdErr.readLine()) != null) {
      stdErr.append(line + newLineCharacter);
    }

    //Nothing else to read, lets pause for a bit before trying again
    process.waitFor(100, TimeUnit.MILLISECONDS);
  }

  //Read anything left, after the process exited
  while ((line = brStdOut.readLine()) != null) {
    stdOut.append(line + newLineCharacter);
  }

  //Read anything left, after the process exited
  while ((line = brStdErr.readLine()) != null) {
    stdErr.append(line + newLineCharacter);
  }

  //cleanup
  if (brStdOut != null) {
    brStdOut.close();
  }

  if (brStdErr != null) {
    brStdOut.close();
  }

  //Log non-zero exit values
  if (!ignoreErrors && process.exitValue() != 0) {
    String exMsg = String.format("%s%nprocess.exitValue=%s", stdErr, process.exitValue());
    throw new ExecuteCommandException(exMsg);
  }

} catch (ExecuteCommandException e) {
  throw e;
} catch (Exception e) {
  throw new ExecuteCommandException(stdErr.toString(), e);
} finally {
  //Log the results
  logStandardOut(stdOut.toString());
  logStandardError(stdErr.toString());
}

return stdOut.toString();

}

1
Sagan