Вопрос-ответ

Why does Runtime.exec(String) work for some but not all commands?

Почему Runtime.exec(строка) работает для некоторых, но не для всех команд?

Когда я пытаюсь запустить Runtime.exec(String), определенные команды работают, в то время как другие команды выполняются, но завершаются сбоем или выполняют другие действия, чем в моем терминале. Вот автономный тестовый пример, демонстрирующий эффект.:

public class ExecTest {
static void exec(String cmd) throws Exception {
Process p = Runtime.getRuntime().exec(cmd);

int i;
while( (i=p.getInputStream().read()) != -1) {
System.out.write(i);
}
while( (i=p.getErrorStream().read()) != -1) {
System.err.write(i);
}
}

public static void main(String[] args) throws Exception {
System.out.print("Runtime.exec: ");
String cmd = new java.util.Scanner(System.in).nextLine();
exec(cmd);
}
}

Пример отлично работает, если я заменю команду на echo hello world, но для других команд - особенно тех, которые включают имена файлов с пробелами, как здесь, - я получаю ошибки, даже если команда явно выполняется:

myshell$ javac ExecTest.java && java ExecTest
Runtime.exec: ls -l 'My File.txt'
ls: cannot access 'My: No such file or directory
ls: cannot access File.txt'
: No such file or directory

между тем, копирование-вставка в мою оболочку:

myshell$ ls -l 'My File.txt'
-rw-r--r-- 1 me me 4 Aug 2 11:44 My File.txt

В чем разница? Когда это работает, а когда завершается сбоем? Как мне заставить это работать для всех команд?

Переведено автоматически
Ответ 1

Почему некоторые команды завершаются ошибкой?

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

Когда команды завершаются ошибкой?

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


  1. Оболочка корректно разбивается на кавычки и пробелы


    Это гарантирует, что имя файла в "My File.txt" остается единственным аргументом.


    Runtime.exec(String) наивно разбивается на пробелы и передает это как два отдельных имени файла. Это, очевидно, не удается.


  2. Оболочка расширяет глобусы / подстановочные знаки


    При запуске ls *.doc оболочка перезаписывает его в ls letter.doc notes.doc.


    Runtime.exec(String) не работает, он просто передает их в качестве аргументов.


    ls понятия не имеет, что такое *, поэтому команда завершается с ошибкой.


  3. Оболочка управляет каналами и перенаправлениями.


    При запуске ls mydir > output.txt оболочка открывает "output.txt" для вывода команды и удаляет ее из командной строки, выдавая ls mydir.


    Runtime.exec(String) не работает. Он просто передает их в качестве аргументов.


    ls понятия не имеет, что > означает, поэтому команда завершается ошибкой.


  4. Оболочка расширяет переменные и команды


    Когда вы запускаете ls "$HOME" или ls "$(pwd)", оболочка перезаписывает его в ls /home/myuser.


    Runtime.exec(String) не работает, он просто передает их в качестве аргументов.


    ls понятия не имеет, что $ означает, поэтому команда завершается ошибкой.


Что вы можете сделать вместо этого?

Есть два способа выполнения сколь угодно сложных команд:

Просто и неаккуратно: делегировать командной оболочке.

Вы можете просто использовать Runtime.exec(String[]) (обратите внимание на параметр array) и передать свою команду непосредственно оболочке, которая может выполнить всю тяжелую работу:

// Simple, sloppy fix. May have security and robustness implications
String myFile = "some filename.txt";
String myCommand = "cp -R '" + myFile + "' $HOME 2> errorlog";
Runtime.getRuntime().exec(new String[] { "bash", "-c", myCommand });

Безопасность и робастность: возьмите на себя обязанности командной оболочки.

Это не исправление, которое можно применить механически, но требует понимания модели выполнения Unix, того, что делают оболочки, и как вы можете сделать то же самое. Однако вы можете получить надежное, безопасное и действенное решение, убрав оболочку из общей картины. Этому способствует ProcessBuilder.

Команда из предыдущего примера, которая требует, чтобы кто-то обрабатывал 1. кавычки, 2. переменные и 3. перенаправления, может быть записана как:

String myFile = "some filename.txt";
ProcessBuilder builder = new ProcessBuilder(
"cp", "-R", myFile, // We handle word splitting
System.getenv("HOME")); // We handle variables
builder.redirectError( // We set up redirections
ProcessBuilder.Redirect.to(new File("errorlog")));
builder.start();
java