Рубрики
Без рубрики

Руководство по API java.lang.ProcessBuilder

Углубленный взгляд на использование API ProcessBuilder для управления процессами ОС

Автор оригинала: Jonathan Cook.

1. Обзор

API Process обеспечивает мощный способ выполнения команд операционной системы на Java. Тем не менее, он имеет несколько вариантов, которые могут сделать его громоздким для работы.

В этом учебнике Мы посмотрим, как Java облегчает это с помощью ПроцессСтроитель API.

2. ProcessBuilder API

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

Вот несколько распространенных сценариев, где мы могли бы использовать этот API:

  • Найти текущую версию Java
  • Настройка пользовательской карты ценностей ключей для нашей среды
  • Измените рабочий каталог того, где работает наша команда оболочки
  • Перенаправление входных и выходных потоков на пользовательские замены
  • Унаследовать оба потока текущего процесса JVM
  • Выполнить команду оболочки из кода Java

Мы посмотрим на практические примеры для каждого из них в более поздних разделах.

Но прежде чем погрузиться в рабочий код, давайте посмотрим, какую функциональность предоставляет этот API.

2.1. Краткое описание метода

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

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

  • Мы можем переопределить рабочий каталог текущего процесса по умолчанию, позвонив в каталог метод и прохождение Файл объект. По умолчанию текущий рабочий каталог устанавливается на значение, возвращенное user.dir системное .

  • Если мы хотим получить текущие переменные среды, мы можем просто назвать окружающей среды метод. Он возвращает нам копию текущая среда процесса с использованием System.getenv() но как Карта .

  • Если мы хотим указать, что источник и назначение для нашего стандарта подпроцессора i/O должны быть такими же, как и текущий процесс Java, мы можем использовать унаследоватьIO метод.

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

  • И последнее, но не менее важное, чтобы начать новый процесс с того, что мы настроили, мы просто называем начало () .

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

3. Примеры

Теперь, когда у нас есть базовое понимание ПроцессСтроитель API, давайте пройдите через несколько примеров.

3.1. Использование ProcessBuilder для печати версии Java

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

Process process = new ProcessBuilder("java", "-version").start();

Во-первых, мы создаем ПроцессСтроитель объект передачи значения команды и аргумента конструктору. Далее мы начинаем процесс с использованием начало () метод, чтобы получить Процесс объект.

Теперь давайте посмотрим, как обрабатывать выход:

List results = readOutput(process.getInputStream());

assertThat("Results should not be empty", results, is(not(empty())));
assertThat("Results should contain java version: ", results, hasItem(containsString("java version")));

int exitCode = process.waitFor();
assertEquals("No errors should be detected", 0, exitCode);

Здесь мы читаем процесс вывода и проверки содержания, что мы ожидаем. На последнем этапе мы ждем завершения процесса с использованием process.waitFor () .

После завершения процесса значение возврата говорит нам, был ли процесс успешным или нет .

Несколько важных моментов, которые нужно иметь в виду:

  • Аргументы должны быть в правильном порядке
  • Кроме того, в этом примере используется рабочий каталог и среда по умолчанию
  • Мы сознательно не звоним process.waitFor () до тех пор, пока мы не прочитаем вывод, потому что буфер вывода может затормозить процесс
  • Мы сделали предположение, что Ява команда доступна через PATH переменная

3.2. Начало процесса с измененной среды

В следующем примере мы увидим, как изменить рабочую среду.

Но прежде чем мы сделаем это, давайте начнем с взглянуть на вид информации, которую мы можем найти в среде по умолчанию :

ProcessBuilder processBuilder = new ProcessBuilder();        
Map environment = processBuilder.environment();
environment.forEach((key, value) -> System.out.println(key + value));

Это просто распечатывает каждую из переменных записей, которые предоставляются по умолчанию:

PATH/usr/bin:/bin:/usr/sbin:/sbin
SHELL/bin/bash
...

Теперь мы собираемся добавить новую переменную среды к нашим ПроцессСтроитель объект и запустить команду, чтобы выработать его значение:

environment.put("GREETING", "Hola Mundo");

processBuilder.command("/bin/bash", "-c", "echo $GREETING");
Process process = processBuilder.start();

Давайте разложим шаги, чтобы понять, что мы сделали:

  • Добавьте переменную под названием ‘GREETING’ со значением ‘Hola Mundo’ в нашу окружающую среду, которая является Карта<Стринг, Струна>
  • На этот раз вместо того, чтобы использовать конструктора, мы устанавливаем команду и аргументы через команда (Строка… команда) метод непосредственно.
  • Затем мы начинаем наш процесс в соответствии с предыдущим примером.

Чтобы завершить пример, мы проверяем выход содержит наше приветствие:

List results = readOutput(process.getInputStream());
assertThat("Results should not be empty", results, is(not(empty())));
assertThat("Results should contain java version: ", results, hasItem(containsString("Hola Mundo")));

3.3. Начало процесса с измененным рабочим каталогом

Иногда может быть полезно изменить рабочий каталог . В нашем следующем примере мы увидим, как это сделать:

@Test
public void givenProcessBuilder_whenModifyWorkingDir_thenSuccess() 
  throws IOException, InterruptedException {
    ProcessBuilder processBuilder = new ProcessBuilder("/bin/sh", "-c", "ls");

    processBuilder.directory(new File("src"));
    Process process = processBuilder.start();

    List results = readOutput(process.getInputStream());
    assertThat("Results should not be empty", results, is(not(empty())));
    assertThat("Results should contain directory listing: ", results, contains("main", "test"));

    int exitCode = process.waitFor();
    assertEquals("No errors should be detected", 0, exitCode);
}

В приведенной выше примере мы хистим рабочий каталог на src реж, используя метод удобства каталог (Файл каталог) . Затем мы запускаем простую команду перечисления каталогов и проверяем, содержит ли выход поднаправления основные и тестовый .

3.4. Перенаправление стандартного ввода и вывода

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

По умолчанию наш процесс считывает входные данные из трубы. Мы можем получить доступ к этой трубе через выходной поток, возвращенный Process.getOutputStream () .

Однако, как мы увидим в ближайшее время, стандартный выход может быть перенаправлен в другой источник, такой как файл, использующий метод перенаправитьOutput . В этом случае getOutputStream () вернет ProcessBuilder.NullOutputStream .

Давайте вернемся к нашему первоначальному примеру, чтобы распечатать версию Java. Но на этот раз давайте перенаправим выход в файл журнала вместо стандартной выходной трубы:

ProcessBuilder processBuilder = new ProcessBuilder("java", "-version");

processBuilder.redirectErrorStream(true);
File log = folder.newFile("java-version.log");
processBuilder.redirectOutput(log);

Process process = processBuilder.start();

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

В этом последнем фрагменте, мы просто проверить, что getInputStream () действительно нулевой и что содержимое нашего файла, как и ожидалось:

assertEquals("If redirected, should be -1 ", -1, process.getInputStream().read());
List lines = Files.lines(log.toPath()).collect(Collectors.toList());
assertThat("Results should contain java version: ", lines, hasItem(containsString("java version")));

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

File log = tempFolder.newFile("java-version-append.log");
processBuilder.redirectErrorStream(true);
processBuilder.redirectOutput(Redirect.appendTo(log));

Важно также упомянуть о призыве перенаправитьErrorStream (правда). В случае каких-либо ошибок выход ошибки будет объединен в обычный файл вывода процесса.

Мы можем, конечно, указать отдельные файлы для стандартного вывода и стандартного вывода ошибки:

File outputLog = tempFolder.newFile("standard-output.log");
File errorLog = tempFolder.newFile("error.log");

processBuilder.redirectOutput(Redirect.appendTo(outputLog));
processBuilder.redirectError(Redirect.appendTo(errorLog));

3.5. Унаследование ВИО текущего процесса

В этом предпоследнем примере мы увидим унаследоватьIO () метод в действии. Мы можем использовать этот метод, когда хотим перенаправить подпрос процесс I/O на стандартный i/O текущего процесса:

@Test
public void givenProcessBuilder_whenInheritIO_thenSuccess() throws IOException, InterruptedException {
    ProcessBuilder processBuilder = new ProcessBuilder("/bin/sh", "-c", "echo hello");

    processBuilder.inheritIO();
    Process process = processBuilder.start();

    int exitCode = process.waitFor();
    assertEquals("No errors should be detected", 0, exitCode);
}

В приведеном выше примере, используя унаследоватьIO () метод, который мы видим выход простой команды в консоли в нашем IDE.

В следующем разделе мы посмотрим, какие дополнения были внесли в проект ПроцессСтроитель API на Java 9.

4. Java 9 Дополнения

Java 9 представила концепцию трубопроводов ПроцессСтроитель API:

public static List startPipeline​(List builders)

Использование startPipeline метод, который мы можем передать список ПроцессСтроитель Объектов. Затем этот статический метод запустит Процесс для каждого ПроцессСтроитель . Таким образом, создание конвейера процессов, связанных их стандартным выходом и стандартными входных потоков.

Например, если мы хотим запустить что-то вроде этого:

find . -name *.java -type f | wc -l

Мы создадим строитель процессов для каждой изолированной команды и с сочиним их в конвейер:

@Test
public void givenProcessBuilder_whenStartingPipeline_thenSuccess()
  throws IOException, InterruptedException {
    List builders = Arrays.asList(
      new ProcessBuilder("find", "src", "-name", "*.java", "-type", "f"), 
      new ProcessBuilder("wc", "-l"));

    List processes = ProcessBuilder.startPipeline(builders);
    Process last = processes.get(processes.size() - 1);

    List output = readOutput(last.getInputStream());
    assertThat("Results should not be empty", output, is(not(empty())));
}

В этом примере мы ищем все файлы Java внутри src каталога и трубопроводов результаты в другой процесс, чтобы подсчитать их.

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

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

Подводя итог, в этом учебнике мы изучили java.lang.ProcessBuilder API в деталях.

Во-первых, мы начали с объяснения того, что можно сделать с API, и обобщили наиболее важные методы.

Далее мы взяли ряд практических примеров. Наконец, мы рассмотрели, какие новые дополнения были введены в API в Java 9.

Как всегда, полный исходный код статьи доступен более на GitHub .