Почему Runtime.exec(String) работает для некоторых, но не для всех команд?
Когда я пытаюсь бежать 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)
не выполняется в оболочке. Оболочка выполняет много общих служб поддержки для программ, и когда оболочка не собирается их выполнять, команда не выполнится.
Когда команды терпят неудачу?
Команда не будет выполнена всякий раз, когда это зависит от особенностей оболочки. Оболочка делает много общих, полезных вещей, о которых мы обычно не думаем:
Оболочка корректно расщепляется на кавычки и пробелы
Это гарантирует, что имя файла в
"My File.txt"
остается один аргумент.Runtime.exec(String)
наивно разделяется на пробелы и передает это как два отдельных имени файла. Это явно не удается.Оболочка расширяет глобусы / подстановочные знаки
Когда ты бежишь
ls *.doc
оболочка переписывает это вls letter.doc notes.doc
,Runtime.exec(String)
нет, он просто передает их в качестве аргументов.ls
понятия не имеет, что*
есть, поэтому команда не работает.Оболочка управляет каналами и перенаправлениями.
Когда ты бежишь
ls mydir > output.txt
оболочка открывает "output.txt" для вывода команды и удаляет его из командной строки, даваяls mydir
,Runtime.exec(String)
не делает. Он просто передает их в качестве аргументов.ls
понятия не имеет, что>
значит, команда не работает.Оболочка раскрывает переменные и команды
Когда ты бежишь
ls "$HOME"
или жеls "$(pwd)"
оболочка переписывает это вls /home/myuser
,Runtime.exec(String)
нет, он просто передает их в качестве аргументов.ls
понятия не имеет, что$
значит, команда не работает.
Что вы можете сделать вместо этого?
Есть два способа выполнить произвольно сложные команды:
Просто и небрежно: делегировать в оболочку.
Вы можете просто использовать Runtime.exec(String[])
(обратите внимание на параметр массива) и передайте свою команду непосредственно в оболочку, которая может выполнить всю тяжелую работу:
// 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();