«1. Обзор

В этом руководстве мы проиллюстрируем два способа выполнения команды оболочки из кода Java.

Первый заключается в использовании класса Runtime и вызове его метода exec.

Второй и более настраиваемый способ заключается в создании и использовании экземпляра ProcessBuilder.

2. Зависимость от операционной системы

Прежде чем мы собираемся создать новый процесс, выполняющий нашу команду оболочки, нам нужно сначала определить операционную систему, в которой работает наша JVM.

Это потому, что в Windows нам нужно запустить нашу команду в качестве аргумента для оболочки cmd.exe, а во всех других операционных системах мы можем запустить стандартную оболочку с именем sh:

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

3. Ввод и вывод

Кроме того, нам нужен способ подключения к входным и выходным потокам нашего процесса.

По крайней мере, выходные данные должны быть использованы, иначе наш процесс не завершится успешно, вместо этого он зависнет.

Давайте реализуем обычно используемый класс StreamGobbler, который использует InputStream:

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, что означает, что он может выполняться любым исполнителем.

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);
Executors.newSingleThreadExecutor().submit(streamGobbler);
int exitCode = process.waitFor();
assert exitCode == 0;

5. ProcessBuilder

Для второй реализации нашей вычислительной задачи мы будем использовать ProcessBuilder. Это предпочтительнее, чем подход Runtime, потому что мы можем настроить некоторые детали.

Например, мы можем:

    изменить рабочий каталог, в котором работает наша команда оболочки, с помощью builder.directory() настроить пользовательскую карту ключ-значение в качестве среды с помощью перенаправления ввода builder.environment() и выходные потоки для пользовательских замен наследуют их оба потокам текущего процесса JVM, используя builder.inheritIO()
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);
Executors.newSingleThreadExecutor().submit(streamGobbler);
int exitCode = process.waitFor();
assert exitCode == 0;

6. Заключение

Как мы видели в этом кратком руководстве, мы можем выполнить оболочку в Java двумя разными способами.

Как правило, если вы планируете настроить выполнение порожденного процесса, например, изменить его рабочий каталог, вам следует рассмотреть возможность использования ProcessBuilder.

Как всегда, вы найдете исходники на GitHub.