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

其中,如果没有引号,该命令将不会在终端上运行,也不会在我上面的流程生成器中运行(出于同样的原因(,但有了引号,它在终端中工作,但不会在上面的流程构建器中工作,因为它是在查找引用名称的文件或目录。

您的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
}

回答基本问题

首先,谈到真正的问题,即尝试构建调用This script中引用的shell脚本的Java代码;t通过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 '%sn' /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 '%sn' "$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 '%sn' "$s""
)

在Charles Duffy对此进行了广泛的研究之后,我意识到我不必像对通过脚本或终端执行的同一命令那样,对嵌入的命令/脚本/替换进行转义。

原因是,当通过流程生成器执行时,没有对命令本身进行处理(这将始终由终端运行的sh/bash进行(。我陷入了这个兔子洞,因为在Java上测试之前,我首先要确保命令在终端中有效。

最新更新