Looking for java Keywords? Try Ask4Keywords

Java Language 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 - Это немного грубо и готово ... но еще раз, недостатки этого подхода не имеют отношения к примеру.