Поиск…


замечания

Обратите внимание, что API рекомендует, чтобы с версии 1.5 предпочтительный способ создания процесса - использовать ProcessBuilder.start() .

Другое важное замечание состоит в том, что значение выхода, созданное waitFor , зависит от выполняемой программы / скрипта. Например, коды выхода, созданные calc.exe , отличаются от notepad.exe .

Простой пример (версия Java <1.5)

Этот пример вызовет калькулятор окон. Важно заметить, что код выхода будет меняться в зависимости от вызываемой программы / скрипта.

package process.example;

import java.io.IOException;

public class App {

    public static void main(String[] args) {
        try {
            // Executes windows calculator
            Process p = Runtime.getRuntime().exec("calc.exe");

            // Wait for process until it terminates
            int exitCode = p.waitFor();

            System.out.println(exitCode);
        } catch (IOException e) {
            e.printStackTrace();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}

Использование класса ProcessBuilder

Класс ProcessBuilder упрощает отправку команды через командную строку. Все, что требуется, это список строк, которые составляют команды для ввода. Вы просто вызываете метод start () в экземпляре ProcessBuilder для выполнения команды.

Если у вас есть программа под названием Add.exe, которая принимает два аргумента и добавляет их, код выглядит примерно так:

List<String> cmds = new ArrayList<>();
cmds.add("Add.exe"); //the name of the application to be run
cmds.add("1"); //the first argument
cmds.add("5"); //the second argument

ProcessBuilder pb = new ProcessBuilder(cmds);

//Set the working directory of the ProcessBuilder so it can find the .exe
//Alternatively you can just pass in the absolute file path of the .exe
File myWorkingDirectory = new File(yourFilePathNameGoesHere);
pb.workingDirectory(myWorkingDirectory);

try {
    Process p = pb.start(); 
} catch (IOException e) {
    e.printStackTrace();
}

Некоторые вещи нужно иметь в виду:

  • Массив команд должен быть массивом String
  • Команды должны быть в порядке (в массиве), в котором они были бы, если бы вы совершили вызов программы в самой командной строке (то есть имя .exe не может идти после первого аргумента
  • При настройке рабочего каталога вам необходимо передать объект File, а не только имя файла в виде строки

Блокировка против неблокирующих вызовов

В общем случае при вызове командной строки программа отправит команду и продолжит ее выполнение.

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

Это можно легко сделать, вызвав метод waitFor() из возвращаемого экземпляра Process .

Пример использования:

//code setting up the commands omitted for brevity...

ProcessBuilder pb = new ProcessBuilder(cmds);

try {
    Process p = pb.start();
    p.waitFor();
} catch (IOException e) {
    e.printStackTrack();
} catch (InterruptedException e) {
    e.printStackTrace();
}

//more lines of code here...

ch.vorburger.exec

Запуск внешних процессов из Java с использованием исходного API java.lang.ProcessBuilder напрямую может быть немного громоздким. Библиотека Apache Commons Exec упрощает работу. Библиотека ch.vorburger.exec далее распространяется на Commons Exec, чтобы сделать ее по-настоящему удобной:

 ManagedProcess proc = new ManagedProcessBuilder("path-to-your-executable-binary")
     .addArgument("arg1")
     .addArgument("arg2")
     .setWorkingDirectory(new File("/tmp"))
     .setDestroyOnShutdown(true)
     .setConsoleBufferMaxLines(7000)
     .build();

proc.start();
int status = proc.waitForExit();
int status = proc.waitForExitMaxMsOrDestroy(3000);
String output = proc.getConsole();

proc.startAndWaitForConsoleMessageMaxMs("started!", 7000);
// use service offered by external process...
proc.destroy();

Pitfall: Runtime.exec, Process и ProcessBuilder не понимают синтаксис оболочки

Runtime.exec(String ...) и Runtime.exec(String) позволяют выполнять команду как внешний процесс 1 . В первой версии вы указываете имя команды и аргументы команды как отдельные элементы массива строк, а среда выполнения Java запрашивает систему выполнения ОС для запуска внешней команды. Вторая версия обманчиво проста в использовании, но у нее есть некоторые подводные камни.

Прежде всего, это пример использования безопасного использования exec(String) :

Process p = Runtime.exec("mkdir /tmp/testDir");
p.waitFor();
if (p.exitValue() == 0) {
    System.out.println("created the directory");
}

Пробелы в дорожках

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

Process p = Runtime.exec("mkdir " + dirPath);
// ...

Обычно это будет работать, но это не удастся, если dirPath (например) «/ home / user / My Documents». Проблема в том, что exec(String) разбивает строку на команду и аргументы, просто просматривая пробелы. Командная строка:

"mkdir /home/user/My Documents"

будут разделены на:

"mkdir", "/home/user/My", "Documents"

и это приведет к сбою команды mkdir, поскольку она ожидает один аргумент, а не два.

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

"mkdir \"/home/user/My Documents\""

будут разделены на:

"mkdir", "\"/home/user/My", "Documents\""

Дополнительные символы двойной кавычки, которые были добавлены в попытке «процитировать» пробелы, рассматриваются как любые другие символы без пробелов. В самом деле, все, что мы цитируем или избегаем пробелов, будет терпеть неудачу.

Способом решения этих конкретных проблем является использование перегрузки exec(String ...) .

Process p = Runtime.exec("mkdir", dirPath);
// ...

Это будет работать, если dirpath содержит пробельные символы, потому что эта перегрузка exec не пытается разделить аргументы. Строки передаются в системный вызов OS exec как есть.

Перенаправление, конвейеры и другой синтаксис оболочки

Предположим, что мы хотим перенаправить вход или выход внешней команды или запустить конвейер. Например:

Process p = Runtime.exec("find / -name *.java -print 2>/dev/null");

или же

Process p = Runtime.exec("find source -name *.java | xargs grep package");

(В первом примере перечислены имена всех файлов Java в файловой системе, а второй печатает операторы package 2 в файлах Java в «исходном» дереве.)

Они не будут работать должным образом. В первом случае команда «find» будет запущена с «2> / dev / null» в качестве аргумента команды. Это не будет интерпретироваться как перенаправление. Во втором примере символ канала («|») и последующие за ним работы будут переданы команде «find».

Проблема здесь в том, что методы exec и ProcessBuilder не понимают никакого синтаксиса оболочки. Это включает в себя перенаправления, трубопроводы, расширение переменных, подтягивание и т. Д.

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

Process p = Runtime.exec("bash", "-c", 
                         "find / -name *.java -print 2>/dev/null");

или же

Process p = Runtime.exec("bash", "-c", 
                         "find source -name \\*.java | xargs grep package");

Но обратите внимание, что во втором примере нам нужно было избежать символа подстановки («*»), потому что мы хотим, чтобы шаблон был интерпретирован «find», а не оболочкой.

Команды встроенных команд не работают

Предположим, что следующие примеры не будут работать в системе с UNIX-подобной оболочкой:

Process p = Runtime.exec("cd", "/tmp");     // Change java app's home directory

или же

Process p = Runtime.exec("export", "NAME=value");  // Export NAME to the java app's environment

Есть несколько причин, почему это не сработает:

  1. Команды «cd» и «export» - это команды, встроенные в оболочку. Они не существуют как отдельные исполняемые файлы.

  2. Для встроенных оболочек оболочки делать то, что они должны делать (например, изменить рабочий каталог, обновить среду), им необходимо изменить место, где это состояние находится. Для обычного приложения (включая приложение Java) состояние связано с процессом приложения. Так, например, дочерний процесс, который запускал команду «cd», не мог изменить рабочий каталог его родительского «java» процесса. Аналогично, один процесс exec не может изменить рабочий каталог для процесса, который следует за ним.

Это рассуждение применимо ко всем командам встроенной оболочки.


1 - Вы также можете использовать ProcessBuilder , но это не относится к точке этого примера.

2 - Это немного грубо и готово ... но еще раз, недостатки этого подхода не имеют отношения к примеру.



Modified text is an extract of the original Stack Overflow Documentation
Лицензировано согласно CC BY-SA 3.0
Не связан с Stack Overflow