首页 > 解决方案 > 在 Java 中执行 /usr/bin/env bash -c "command"

问题描述

如果我/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)"

如果没有引号,该命令将不会在终端上运行,也不会在我上面的流程构建器中运行(出于相同的原因),但是如果使用引号,它可以在终端中运行,但不能在上述流程构建器中运行,因为它是在查找文件或该引用名称的目录。

标签: javabashprocessbuilder

解决方案


回答基本问题

首先,谈到真正的问题,即尝试构建调用此脚本中引用的 shell 脚本的 Java 代码将无法通过 docker exec 工作

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\""
)

推荐阅读