Javaで OSコマンドを実行 - JBang Jash -


jbang-jash とは

Java でOSコマンドを実行するには、ProcessBuilder を使います(旧来はRuntime)。以下のような感じですね。

ProcessBuilder builder = new ProcessBuilder("java", "-version");
Process process = builder.start();
InputStream stream = process.getErrorStream();

コマンドの実行結果は、Process から標準出力または標準エラー出力を InputStream として得ることができます(builder.redirectErrorStream(true) とすることで標準エラー出力を標準出力にマージすることも可能です)。 しかし、InputStream の取り回しは意外と面倒なものです。


jbang-jash は ProcessBuilder をラップし、OSコマンドの結果を Java Stream として扱うことを可能とする軽量なライブラリです(jash は、Java + Shell で、Jazz と発音するようです)。

github.com

以下のように使うことができます。

$("java -version").stream().forEach(System.out::println);

jbang と付いていますが、JBang とは独立しており、単独のライブラリとして利用することができます。


jbang-jash の使い方

以下の依存を追加します。

dependencies {
    implementation("dev.jbang:jash:0.0.3")
}

dev.jbang.jash.Jash で公開されているスタティックメソッドが入口になるため、以下でインポートしておきます。

import static io.ongres.jash.Jash.*;

デフォルトのシェルでコマンドを実行するには以下のようにします。

$("echo hello").stream().forEach(System.out::println); // hello

$()shell() のエイリアスです。以下でも同様です。

shell("echo hello").stream().forEach(System.out::println); // hello

デフォルトのシェルは、Linux の場合は、SHELL 環境変数から、Windows の場合は ComSpec 環境変数から取得され、一般的には以下のコマンドが実行されることになります。

# Linux
/bin/bash -c echo hello

# Windows
C:\WINDOWS\system32\cmd.exe /C echo hello


ビルダを経由して以下のようにすることもできます。

JashBuilder builder = new JashBuilder("sh").args("-c", "echo hello");
builder.start().stream().forEach(System.out::println);

上記は、Jash.builder() としてエイリアスされているので、以下と同様です。

Jash.builder("sh", "-c", "echo hello").start()
        .stream().forEach(System.out::println);

Jash.builder().start() までを同時に行う Jash.start() があるので、以下でも同様です。

Jash.start("sh", "-c", "echo hello")
    .stream().forEach(System.out::println);

しかし、多くの場合は、Jash.shell("..").stream() だけで十分でしょう。


その他の使い方

pipe() でパイプ処理できます。

Jash.shell("echo hello; echo world").pipe("cat", "--number")
        .stream().forEach(System.out::println);

JashAutoCloseable を実装しているため、通常は以下のように try 句と共に使うことになるでしょう。

try (Jash jash = Jash.shell("echo hello")) {
    jash.stream().forEach(System.out::println);
}


コマンドの終了コードが非ゼロの場合 Jash は例外を投げます。

終了コードを加味しない場合は withAnyExitCode() を指定します。

Jash.shell("...").withAnyExitCode().stream()...

許容する終了コードを明示的に指定することもできます。

Jash.shell("...").withAllowedExitCodes(1, 2).stream()...


Jash.withTimeout() でタイムアウトを指定することができます。

try (var jash = Jash.shell("...").withTimeout(Duration.of(1, ChronoUnit.SECONDS))) {
    jash.stream()...
}