1. 概要

このチュートリアルでは、Javaコード内からシェルコマンドを実行する2つの方法を説明します。

1つ目は、 Runtime クラスを使用して、そのexecメソッドを呼び出すことです。

2番目のよりカスタマイズ可能な方法は、ProcessBuilderインスタンスを作成して使用することです。

2. オペレーティングシステムの依存関係

シェルコマンドを実行する新しいProcessを作成する前に、まずJVMが実行されているオペレーティングシステムを特定する必要があります。

これは、 Windows では、 cmd.exe シェルの引数としてコマンドを実行する必要があり、他のすべてのオペレーティングシステムでは、shと呼ばれる標準シェルを発行できるためです。 :

boolean isWindows = System.getProperty("os.name")
  .toLowerCase().startsWith("windows");

3. 入出力

Furthermore we need a way to hook into the input and output streams of our process.

少なくとも出力を消費する必要があります–そうしないと、プロセスが正常に戻らず、代わりにハングします。

InputStreamを消費するStreamGobblerと呼ばれる一般的に使用されるクラスを実装しましょう。

private static class StreamGobbler implements Runnable {
    private InputStream inputStream;
    private Consumer<String> consumer;

    public StreamGobbler(InputStream inputStream, Consumer<String> consumer) {
        this.inputStream = inputStream;
        this.consumer = consumer;
    }

    @Override
    public void run() {
        new BufferedReader(new InputStreamReader(inputStream)).lines()
          .forEach(consumer);
    }
}

注:このクラスは Runnable インターフェースを実装しています。つまり、任意のExecutorで実行できます。

4. Runtime.exec()

Runtime.exec()へのメソッド呼び出しは、新しいサブプロセスを生成するための単純な、まだカスタマイズできない方法です。

次の例では、ユーザーのホームディレクトリのディレクトリリストを要求し、それをコンソールに出力します。

String homeDirectory = System.getProperty("user.home");
Process process;
if (isWindows) {
    process = Runtime.getRuntime()
      .exec(String.format("cmd.exe /c dir %s", homeDirectory));
} else {
    process = Runtime.getRuntime()
      .exec(String.format("sh -c ls %s", homeDirectory));
}
StreamGobbler streamGobbler = 
  new StreamGobbler(process.getInputStream(), System.out::println);
Future<?> future = Executors.newSingleThreadExecutor().submit(streamGobbler);

int exitCode = process.waitFor();
assert exitCode == 0;

future.get(); // waits for streamGobbler to finish

When you want to use the result of the process, make sure to call the future.get() method to wait for the computation to complete.

5. ProcessBuilder

コンピューティング問題の2番目の実装では、ProcessBuilderを使用します。 これは、 Runtime アプローチよりも優先されます。これは、一部の詳細をカスタマイズできるためです。

たとえば、次のことができます。

  • builder.directory()を使用して、シェルコマンドが実行されている作業ディレクトリを変更します
  • builder.environment()を使用して、カスタムキー値マップを環境として設定します
  • 入力ストリームと出力ストリームをカスタム置換にリダイレクトします
  • builder.inheritIO()を使用して、両方を現在のJVMプロセスのストリームに継承します。
ProcessBuilder builder = new ProcessBuilder();
if (isWindows) {
    builder.command("cmd.exe", "/c", "dir");
} else {
    builder.command("sh", "-c", "ls");
}
builder.directory(new File(System.getProperty("user.home")));
Process process = builder.start();
StreamGobbler streamGobbler = 
  new StreamGobbler(process.getInputStream(), System.out::println);
Future<?> future = Executors.newSingleThreadExecutor().submit(streamGobbler);
int exitCode = process.waitFor();
assert exitCode == 0;
future.get(10, TimeUnit.SECONDS)

6. 結論

このクイックチュートリアルで見たように、Javaで2つの異なる方法でシェルコマンドを実行できます。

一般に、生成されたプロセスの実行をカスタマイズする場合、たとえば、作業ディレクトリを変更する場合は、ProcessBuilderの使用を検討する必要があります。

いつものように、ソースはGitHubにあります。