Выполнение сценария оболочки с пустым аргументом из Java - PullRequest
4 голосов
/ 23 мая 2011

У меня есть bash-скрипт, который принимает пару тестов аргументов и запускает команду, как показано ниже

call script => /bin/bash myscript arg1 arg2 arg3 arg4
comm called => command -a arg1 -b arg2 -c arg3 -d arg4

если параметр пустой, то эта опция не вызывается.

call script => /bin/bash myscript arg1 arg2 '' arg4
comm called => command -a arg1 -b arg2 -d arg4

Я могу добиться этого, используя следующую строку в скрипте для проверки аргументов

if test "${arg3:-t}" != "t" ; then

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

Это перестает работать, когда я вызываю этот скрипт из Java с помощью exec.

Process p = Runtime.getRuntime().exec("/bin/bash myscript arg1 arg2 '' arg4");
expected command => command -a arg1 -b arg2 -d arg4 (as in above example)
actual command   => command -a arg1 -b arg2 -c '' -d arg4

Я не могу понять, почему это происходит. В чем проблема? В сценарии оболочки или в том, как команда выполняется из Java?

Как это можно исправить?

Ответы [ 4 ]

4 голосов
/ 23 мая 2011

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

Краткий ответ - использовать String [] версию Runtime.exec:

Runtime.getRuntime().exec(
    new String[] {"/bin/bash", "myscript", "arg1", "arg2", "",  "arg4"});

Если у вас есть другие места, где разбор аргументов более запутан, чем этот, вы можете передать разбор строки в bash и выполнить:

Runtime.getRuntime().exec(
    new String[] {"/bin/bash", "-c", "/bin/bash myscript arg1 arg2 '' arg4"});

Если вы конвертируете что-то, что выполняет огромное количество сложных перенаправлений, таких как 2>&1 или настраивает весь конвейер, вам может понадобиться этот bash -c трюк.

EDIT:

Чтобы понять, что здесь происходит, вы должны понимать, что когда код пользовательского пространства говорит ядру «загрузить этот исполняемый файл с этими аргументами и запустить процесс, основанный на этом» (*), то, что передается ядру, является исполняемый файл, массив строк для аргументов (0-й / первый элемент этого массива аргументов - это имя исполняемого файла, за исключением случаев, когда выполняется что-то странное), а также массив строк для среды.

Что делает bash, когда видит эту строку:

/bin/bash myscript arg1 arg2 '' arg4

означает «Хорошо, /bin/bash не является встроенной функцией, поэтому я выполняю что-то. Давайте соберем массив аргументов для подпроцесса, используя мои алгоритмы синтаксического анализа строк, которые знают о кавычках и еще много чего». Затем Bash определяет, что аргументы для передачи ядра новому процессу:

  • /bin/bash

  • myscript

  • arg1

  • arg2

  • (пустая строка)

  • arg4

Теперь bash имеет довольно сложные алгоритмы обработки строк, которые он применяет здесь - он принимает два разных типа кавычек, он будет делать расширение $VAR, когда это происходит вне строк или внутри двойных кавычек, он заменяет подкоманды в обратных кавычках с выходом и т. д.

Java не делает ничего сложного, когда вы вызываете одностроковую версию exec. Он просто создает новый StringTokenizer и использует его для разбиения строки, которую вы даете, в аргументы. Этот класс ничего не знает о кавычках; это разбивает эту строку на:

  • /bin/bash

  • myscript

  • arg1

  • arg2

  • '' (строка из двух символов, каждый из которых является одинарной кавычкой)

  • arg4

Затем Java вызывает String[] версию exec. (Ну, один из них)

Заметки для людей, выбирающих гниды:

(*) Да, я намеренно исключаю разницу между системными вызовами fork и execve. Притворись, что они один звонок на данный момент.

1 голос
/ 23 мая 2011

Попробуйте это:

ProcessBuilder pb = new ProcessBuilder("/bin/bash", "myscript", "arg1", "arg2", "",  "arg4");
Process proc = pb.start();
0 голосов
/ 23 мая 2011

Как насчет этого?

javaParams=""
[ -n "$1" ] && javaParams="-a '$1'"
[ -n "$2" ] && javaParams="$javaParams -b '$2'"
[ -n "$3" ] && javaParams="$javaParams -c '$3'"
[ -n "$4" ] && javaParams="$javaParams -d '$4'"
command $javaParams

[ -n "$1" ] && javaParams="-a $1"

эквивалентно:

if [ -n "$1" ]
then
    javaParams="-a $1"
fi

Тест -n определяет, имеет ли предоставленная строка нулевую длину или нет.

0 голосов
/ 23 мая 2011

Вы не должны использовать Runtime.exec. Java 5 представила ProcessBuilder , который вы должны использовать вместо этого. Это позволяет лучше контролировать запуск процесса и настройку среды.

ProcessBuilder pb = new ProcessBuilder("/bin/bash", "myscript", "arg1", "arg2", "", "arg4");
pb.start();
...