Вступление
В этой статье мы рассмотрим, как мы можем использовать Runtime
и
ProcessBuilder
для выполнения команд и сценариев оболочки с помощью
Java.
Мы используем компьютеры для автоматизации многих вещей в нашей повседневной работе. Системные администраторы все время запускают множество команд, некоторые из которых очень часто повторяются и требуют минимальных изменений между запусками.
Этот процесс тоже созрел для автоматизации. Нет необходимости запускать все вручную. Используя Java, мы можем запускать одну или несколько команд оболочки, выполнять сценарии оболочки, запускать терминал / командную строку, устанавливать рабочие каталоги и управлять переменными среды с помощью основных классов.
Runtime.exec ()
Класс Runtime
в Java - это класс высокого уровня, присутствующий в
каждом отдельном приложении Java. Через него само приложение
взаимодействует с окружающей средой, в которой оно находится.
Извлекая среду выполнения, связанную с нашим приложением, с помощью
getRuntime()
, мы можем использовать метод exec()
для
непосредственного выполнения команд или запуска файлов .bat
/ .sh
Метод exec()
предлагает несколько вариантов с перегрузкой:
public Process exec(String command)
- выполняет команду, содержащуюся вcommand
в отдельном процессе.public Process exec(String command, String[] envp)
- выполняетcommand
с массивом переменных среды. Они представлены в виде массива строк в форматеname=value
public Process exec(String command, String[] envp, File dir)
- выполняетcommand
с указанными переменными среды из каталогаdir
public Process exec(String cmdArray[])
- выполняет команду в виде массива строк.public Process exec(String cmdArray[], String[] envp)
- выполняет команду с указанными переменными среды.public Process exec(String cmdarray[], String[] envp, File dir)
- выполняет команду с указанными переменными среды из каталогаdir
Стоит отметить, что эти процессы запускаются извне из интерпретатора и будут зависеть от системы.
Также стоит отметить разницу между String command
String и
String cmdArray[]
. Они добиваются того же. command
в любом случае
разбивается на массив, поэтому использование любой из этих двух команд
должно дать одинаковые результаты.
Вам решать, что вы хотите использовать - exec("dir /folder")
или
exec(new String[]{"dir", "/folder"}
Давайте напишем несколько примеров, чтобы увидеть, чем эти перегруженные методы отличаются друг от друга.
Выполнение команды из строки
Начнем с самого простого из трех подходов:
Process process = Runtime.getRuntime().exec("ping www.stackabuse.com");
Запуск этого кода выполнит команду, которую мы предоставили в формате String. Однако мы ничего не видим, когда запускаем это.
Чтобы проверить, правильно ли это работает, мы хотим получить объект
process
Давайте воспользуемся BufferedReader
чтобы посмотреть, что
происходит:
public static void printResults(Process process) throws IOException {
BufferedReader reader = new BufferedReader(new InputStreamReader(process.getInputStream()));
String line = "";
while ((line = reader.readLine()) != null) {
System.out.println(line);
}
}
Теперь, когда мы запускаем этот метод после метода exec()
, он должен
дать что-то вроде:
Pinging www.stackabuse.com [104.18.57.23] with 32 bytes of data:
Reply from 104.18.57.23: bytes=32 time=21ms TTL=56
Reply from 104.18.57.23: bytes=32 time=21ms TTL=56
Reply from 104.18.57.23: bytes=32 time=21ms TTL=56
Reply from 104.18.57.23: bytes=32 time=21ms TTL=56
Ping statistics for 104.18.57.23:
Packets: Sent = 4, Received = 4, Lost = 0 (0% loss),
Approximate round trip times in milli-seconds:
Minimum = 21ms, Maximum = 21ms, Average = 21ms
Имейте в виду, что нам придется извлекать информацию о Process
экземпляров процесса, когда мы будем рассматривать другие примеры.
Укажите рабочий каталог
Если вы хотите запустить команду, скажем, из определенной папки, мы сделаем что-то вроде:
Process process = Runtime.getRuntime()
.exec("cmd /c dir", null, new File("C:\\Users\\"));
//.exec("sh -c ls", null, new File("Pathname")); for non-Windows users
printResults(process);
Здесь мы предоставили метод exec()
с command
, null
для новых
переменных среды и new File()
который установлен в качестве нашего
рабочего каталога.
Стоит отметить добавление cmd /c
перед такой командой, как dir
Поскольку я работаю в Windows, это открывает cmd
а /c
выполняет
следующую команду. В данном случае это dir
.
Причина , почему это не было обязательным для ping
, например, но
является обязательным для этого примера хорошо
ответил
на пользователем SO.
Выполнение предыдущего фрагмента кода приведет к:
Volume in drive C has no label.
Volume Serial Number is XXXX-XXXX
Directory of C:\Users
08/29/2019 05:01 PM <DIR> .
08/29/2019 05:01 PM <DIR> ..
08/18/2016 09:11 PM <DIR> Default.migrated
08/29/2019 05:01 PM <DIR> Public
05/15/2020 11:08 AM <DIR> User
0 File(s) 0 bytes
5 Dir(s) 212,555,214,848 bytes free
Давайте посмотрим, как мы могли бы предоставить предыдущую команду в нескольких отдельных частях вместо одной строки:
Process process = Runtime.getRuntime().exec(
new String[]{"cmd", "/c", "dir"},
null,
new File("C:\\Users\\"));
printResults(process);
Выполнение этого фрагмента кода также приведет к:
Volume in drive C has no label.
Volume Serial Number is XXXX-XXXX
Directory of C:\Users
08/29/2019 05:01 PM <DIR> .
08/29/2019 05:01 PM <DIR> ..
08/18/2016 09:11 PM <DIR> Default.migrated
08/29/2019 05:01 PM <DIR> Public
05/15/2020 11:08 AM <DIR> User
0 File(s) 0 bytes
5 Dir(s) 212,542,808,064 bytes free
В конечном счете, независимо от подхода - при использовании одного массива String или String вводимая вами команда всегда будет разбита на массив перед обработкой базовой логикой.
Какой из них вы хотите использовать, зависит только от того, какой из них вам удобнее читать.
Использование переменных среды
Давайте посмотрим, как мы можем использовать переменные среды:
Process process = Runtime.getRuntime().exec(
"cmd /c echo %var1%",
new String[]{"var1=value1"});
printResults(process);
Мы можем предоставить столько переменных среды, сколько захотим, в
массиве String. Здесь мы только что напечатали значение var1
с помощью
echo
.
Запуск этого кода вернет:
value1
Запуск файлов .bat и .sh
Иногда гораздо проще выгрузить все в файл и запустить этот файл вместо того, чтобы добавлять все программно.
В зависимости от вашей операционной системы вы можете использовать файлы
.bat
или .sh
Создадим его с содержимым:
echo Hello World
Затем воспользуемся тем же подходом, что и раньше:
Process process = Runtime.getRuntime().exec(
"cmd /c start file.bat",
null,
new File("C:\\Users\\User\\Desktop\\"));
Это откроет командную строку и запустит .bat
в установленном нами
рабочем каталоге.
Выполнение этого кода, безусловно, приведет к:
{.ezlazyload}
Позаботившись обо всех перегруженных exec()
, давайте посмотрим на
ProcessBuilder
и то, как мы можем выполнять команды с его помощью.
ProcessBuilder
ProcessBuilder
- это базовый механизм, который запускает команды,
когда мы используем метод Runtime.getRuntime().exec()
:
/**
* Executes the specified command and arguments in a separate process with
* the specified environment and working directory.
*...
*/
public Process exec(String[] cmdarray, String[] envp, File dir) throws IOException {
return new ProcessBuilder(cmdarray)
.environment(envp)
.directory(dir)
.start();
}
[JavaDocs для класса Runtime
]{.small}
Взгляд на то, как ProcessBuilder
принимает наши входные данные из
exec()
и запускает команду, также дает нам хорошее представление о
том, как его использовать.
Он принимает String[] cmdarray
, и этого достаточно для его запуска. В
качестве альтернативы мы можем предоставить ему необязательные
аргументы, такие как String[] envp
и File dir
.
Давайте рассмотрим эти варианты.
ProcessBuilder: выполнение команды из строк
Вместо того, чтобы предоставить одну строку, такую как cmd /c dir
, в
этом случае нам придется ее разбить. Например, если бы мы хотели
перечислить файлы в каталоге C:/Users
как раньше, мы бы сделали:
ProcessBuilder processBuilder = new ProcessBuilder();
processBuilder.command("cmd", "/c", "dir C:\\Users");
Process process = processBuilder.start();
printResults(process);
Чтобы фактически выполнить Process
, мы запускаем команду start()
и
присваиваем возвращенное значение экземпляру Process
Запуск этого кода даст:
Volume in drive C has no label.
Volume Serial Number is XXXX-XXXX
Directory of C:\Users
08/29/2019 05:01 PM <DIR> .
08/29/2019 05:01 PM <DIR> ..
08/18/2016 09:11 PM <DIR> Default.migrated
08/29/2019 05:01 PM <DIR> Public
05/15/2020 11:08 AM <DIR> User
0 File(s) 0 bytes
5 Dir(s) 212,517,294,080 bytes free
Однако этот подход ничем не лучше предыдущего. Что полезно с
ProcessBuilder
, так это его настраиваемость. Мы можем настраивать
вещи программно, а не только с помощью команд.
ProcessBuilder: укажите рабочий каталог
Вместо того, чтобы указывать рабочий каталог с помощью команды, давайте установим его программно:
processBuilder.command("cmd", "/c", "dir").directory(new File("C:\\Users\\"));
Здесь мы установили такой же рабочий каталог, как и раньше, но мы вынесли это определение из самой команды. Выполнение этого кода даст тот же результат, что и последний пример.
ProcessBuilder: переменные среды
Используя ProcessBuilder
, легко получить список переменных среды в
виде Map
. Также легко установить переменные среды, чтобы ваша
программа могла их использовать.
Давайте получим доступные в настоящее время переменные среды, а затем добавим некоторые для дальнейшего использования:
ProcessBuilder processBuilder = new ProcessBuilder();
Map<String, String> environmentVariables = processBuilder.environment();
environmentVariables.forEach((key, value) -> System.out.println(key + value));
Здесь мы упаковали возвращенные переменные среды в Map
и запустили на
ней forEach()
чтобы распечатать значения на нашей консоли.
Запуск этого кода даст список переменных среды, которые есть на вашем компьютере:
DriverDataC:\Windows\System32\Drivers\DriverData
HerokuPathE:\Heroku
ProgramDataC:\ProgramData
...
Теперь давайте добавим в этот список переменную окружения и воспользуемся ею:
environmentVariables.put("var1", "value1");
processBuilder.command("cmd", "/c", "echo", "%var1%");
Process process = processBuilder.start();
printResults(process);
Запуск этого кода даст:
value1
Конечно, после завершения работы программы эта переменная не останется в списке.
ProcessBuilder: запуск файлов .bat и .sh
Если вы снова хотите запустить файл, мы просто предоставим
ProcessBuilder
необходимую информацию:
processBuilder
.command("cmd", "/c", "start", "file.bat")
.directory(new File("C:\\Users\\User\\Desktop"));
Process process = processBuilder.start();
Запуск этого кода приводит к открытию командной строки и выполнению
файла .bat
{.ezlazyload}
Заключение
В этой статье мы рассмотрели примеры запуска команд оболочки в Java. Для
этого мы использовали классы Runtime
и ProcessBuilder
Используя Java, мы можем запускать одну или несколько команд оболочки, выполнять сценарии оболочки, запускать терминал / командную строку, устанавливать рабочие каталоги и управлять переменными среды с помощью основных классов.