Автор оригинала: 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();
Во-первых, мы создаем ПроцессСтроитель объект передачи значения команды и аргумента конструктору. Далее мы начинаем процесс с использованием начало () метод, чтобы получить Процесс объект.
Теперь давайте посмотрим, как обрабатывать выход:
Listresults = 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(); Mapenvironment = 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’ в нашу окружающую среду, которая является Карта<Стринг, Струна>
- На этот раз вместо того, чтобы использовать конструктора, мы устанавливаем команду и аргументы через команда (Строка… команда) метод непосредственно.
- Затем мы начинаем наш процесс в соответствии с предыдущим примером.
Чтобы завершить пример, мы проверяем выход содержит наше приветствие:
Listresults = 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(); Listresults = 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()); Listlines = 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 ListstartPipeline(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 .