Выполнение / usr / bin / env bash - c "команды" в Java - PullRequest
1 голос
/ 18 марта 2020

Если я запускаю /usr/bin/env bash -c "docker info" в терминале, я получаю соответствующий вывод. Я пытаюсь повторить это в Java, как показано ниже

ProcessBuilder pb = new ProcessBuilder("/usr/bin/env", "bash", "-c", "\"docker info\"");
pb.redirectErrorStream(true);
pb.start();

Это не удается как bash: docker info: command not found. Я подумал, что это рассматривается как одна команда, и вы можете обойти это, избавившись от этих пропущенных кавычек и заставив их работать. Но если у вас есть не простая команда, такая как docker info, а что-то вроде этого

/usr/bin/env bash -c "grep docker -m1 /proc/self/cgroup|echo \$(read s;s=\${s##*/};s=\${s#*docker-};s=\${s%.scope};echo \$s)"

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

Ответы [ 3 ]

1 голос
/ 18 марта 2020

Ваш ProcessBuilder не смог запустить процесс, потому что двойные кавычки там не принадлежат. Кавычки нужны командной строке, чтобы указать, что docker info - это один аргумент, а не два аргумента. Но при выполнении процесса напрямую, без командной строки, кавычки не имеют особого значения. Аргумент уже является одним аргументом, просто передавая его как одну строку.

Я хотел бы предложить альтернативу. Вам не нужно bash и вам не нужен grep. У вас есть Java. Java поддерживает регулярные выражения просто отлично.

Итак, вот та же функциональность без bash или grep:

Optional<String> matchingLine;
try (Stream<String> lines =
    Files.newBufferedReader(Paths.get("/proc/self/cgroup"),
        Charset.defaultCharset()).lines()) {

    matchingLine = lines.filter(l -> l.contains("docker")).findFirst();
}

if (matchingLine.isPresent()) {
    String line = matchingLine.get();
    line = line.replaceFirst("^.*/", "");
    line = line.replaceFirst("^.*docker-", "");
    line = line.replaceFirst("\\.scope$", "");

    // Do things with 'line' here
}
1 голос
/ 18 марта 2020

Ответ на базовый вопрос

Сначала поговорим о real вопросе о попытке создать Java код, который вызывает сценарий оболочки, указанный в Этот сценарий не будет работать через docker exe c:

ProcessBuilder pb = new ProcessBuilder("/bin/sh", "-c", "shellQuoteWordsDef='shellQuoteWords() { sq=\"'\"'\"'\"; dq='\"'\"'\"'\"'\"'; for arg; do printf \"'\"'\"'%s'\"'\"' \" \"$(printf '\"'\"'%s\\n'\"'\"' \"$arg\" | sed -e \"s@${sq}@${sq}${dq}${sq}${dq}${sq}@g\")\"; done; printf '\"'\"'\\n'\"'\"'; }'; shellQuoteNullSeparatedStream() { xargs -0 sh -c \"${shellQuoteWordsDef};\"' shellQuoteWords \"$@\"' _; }; getProcessData() { systick=$(getconf CLK_TCK); for c in /proc/*/cmdline; do d=${c%/*}; pid=${d##*/}; name=$(awk '/^Name:/ { print $2 }' <\"$d\"/status); uid=$(awk '/^Uid:/ { print $2 }' <\"$d\"/status); pwent=$(getent passwd \"$uid\"); user=${pwent%%:*}; cmdline=$(shellQuoteNullSeparatedStream <\"$c\"); starttime=$(awk -v systick=\"$systick\" '{print int($22 / systick)}' \"$d\"/stat); uptime=$(awk '{print int($1)}' /proc/uptime); elapsed=$((uptime-starttime)); echo \"$pid $user $elapsed $cmdline\"; done; }; getProcessData");

Эта строка Java была сгенерирована средой выполнения Clojure. Взяв определение переменной getProcessDataDef из этого ответа, я запустил:

$ getProcessDataDef="$getProcessDataDef" lein repl
nREPL server started on port 54512 on host 127.0.0.1 - nrepl://127.0.0.1:54512
REPL-y 0.3.7, nREPL 0.2.12
Clojure 1.8.0
OpenJDK 64-Bit Server VM 11.0.1+13-LTS
    Docs: (doc function-name-here)
          (find-doc "part-of-name-here")
  Source: (source function-name-here)
 Javadoc: (javadoc java-object-or-class-here)
    Exit: Control+D or (exit) or (quit)
 Results: Stored in vars *1, *2, *3, an exception in *e

user=> (System/getenv "getProcessDataDef")
"shellQuoteWordsDef='shellQuoteWords() { sq=\"'\"'\"'\"; dq='\"'\"'\"'\"'\"'; for arg; do printf \"'\"'\"'%s'\"'\"' \" \"$(printf '\"'\"'%s\\n'\"'\"' \"$arg\" | sed -e \"s@${sq}@${sq}${dq}${sq}${dq}${sq}@g\")\"; done; printf '\"'\"'\\n'\"'\"'; }'; shellQuoteNullSeparatedStream() { xargs -0 sh -c \"${shellQuoteWordsDef};\"' shellQuoteWords \"$@\"' _; }; getProcessData() { systick=$(getconf CLK_TCK); for c in /proc/*/cmdline; do d=${c%/*}; pid=${d##*/}; name=$(awk '/^Name:/ { print $2 }' <\"$d\"/status); uid=$(awk '/^Uid:/ { print $2 }' <\"$d\"/status); pwent=$(getent passwd \"$uid\"); user=${pwent%%:*}; cmdline=$(shellQuoteNullSeparatedStream <\"$c\"); starttime=$(awk -v systick=\"$systick\" '{print int($22 / systick)}' \"$d\"/stat); uptime=$(awk '{print int($1)}' /proc/uptime); elapsed=$((uptime-starttime)); echo \"$pid $user $elapsed $cmdline\"; done; }; getProcessData"

Самостоятельная отладка проблемы

Чтобы напечатать ваши буквенные строки, по одной на строку:

printf '%s\n' /usr/bin/env bash -c "grep docker -m1 /proc/self/cgroup|echo \$(read s;s=\${s##*/};s=\${s#*docker-};s=\${s%.scope};echo \$s)"

... выдает в качестве выходных данных:

/usr/bin/env
bash
-c
grep docker -m1 /proc/self/cgroup|echo $(read s;s=${s##*/};s=${s#*docker-};s=${s%.scope};echo $s)

Каждая из этих строк должна быть преобразована в одну строку Java в виде литерала путем добавления начальных и конечных двойных кавычек, а затем добавления обратная косая черта для любых символов, которые должны быть экранированы. Таким образом:

ProcessBuilder pb = new ProcessBuilder(
  "/usr/bin/env",
  "bash",
  "-c",
  "grep docker -m1 /proc/self/cgroup|echo $(read s;s=${s##*/};s=${s#*docker-};s=${s%.scope};echo $s)"
)

Однако , этот код, как правило, довольно глючный. Я настоятельно рекомендую заменить ваш оригинальный bash следующим:

s=$(grep docker -m1 /proc/self/cgroup)
s=${s##*/}
s=${s#*docker-}
s=${s%.scope}
printf '%s\n' "$s"

... как в:

ProcessBuilder pb = new ProcessBuilder(
  "/usr/bin/env",
  "bash",
  "-c",
  "s=$(grep docker -m1 /proc/self/cgroup); s=${s##*/}; s=${s#*docker-}; s=${s%.scope}; printf '%s\n' \"$s\""
)
0 голосов
/ 18 марта 2020

Ну а после Чарльз Даффи интенсивно работал над этим, выводы которого подробно изложены здесь , а также со ссылками в этом ответе, я понял, что мне не нужно избегать встроенных команд / scripts / замены, как я бы регулярно делал для одной и той же команды, выполняемой через скрипт или терминал.

Причина в том, что при выполнении через построитель процессов обработка самой команды не выполняется (что всегда будет сделано sh / bash терминал работал). Я упал в эту кроличью нору, потому что прежде чем проверять ее на Java.

, я проверял, что команда работала в терминале.
...