在 Java 中执行 /usr/bin/env bash -c "command"
Executing /usr/bin/env bash -c "command" in Java
如果我在终端中 运行 /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)"
如果没有引号,该命令将不会 运行 在终端上,也不会在我上面的流程构建器中(出于同样的原因),但是如果有引号,它可以在终端中工作,但不能在上述流程中工作生成器,因为它正在查找具有该引用名称的文件或目录。
回答基本问题
首先,谈谈 真正的 问题,即尝试构建 Java 调用 中引用的 shell 脚本的代码:
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 }' <\"$d\"/status); uid=$(awk '/^Uid:/ { print }' <\"$d\"/status); pwent=$(getent passwd \"$uid\"); user=${pwent%%:*}; cmdline=$(shellQuoteNullSeparatedStream <\"$c\"); starttime=$(awk -v systick=\"$systick\" '{print int( / systick)}' \"$d\"/stat); uptime=$(awk '{print int()}' /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 }' <\"$d\"/status); uid=$(awk '/^Uid:/ { print }' <\"$d\"/status); pwent=$(getent passwd \"$uid\"); user=${pwent%%:*}; cmdline=$(shellQuoteNullSeparatedStream <\"$c\"); starttime=$(awk -v systick=\"$systick\" '{print int( / systick)}' \"$d\"/stat); uptime=$(awk '{print int()}' /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\""
)
您的 ProcessBuilder 无法启动进程,因为双引号不属于那里。命令行需要引号,以指示 docker info
是单个参数而不是两个参数。但是在没有命令行的情况下直接执行进程时,引号没有特殊含义。该参数已经是单个参数,只需将其作为单个字符串传递即可。
我想推荐一个替代方案。你不需要 bash 也不需要 grep。你有 Java。 Javasupports regular expressions就好了
所以,这里是没有 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
}
在 Charles Duffy extensively worked on this, whose findings are detailed 以及在该答案中链接 post 之后,我意识到我不必像我经常执行的相同命令那样转义嵌入式 commands/scripts/substitutions通过脚本或终端。
原因是当通过进程构建器执行时,没有对命令本身进行任何处理(通常由 sh
/bash
终端完成 运行 ).我掉进了这个兔子洞,因为在 Java.
上测试命令之前,我首先要确保该命令在终端中有效
如果我在终端中 运行 /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)"
如果没有引号,该命令将不会 运行 在终端上,也不会在我上面的流程构建器中(出于同样的原因),但是如果有引号,它可以在终端中工作,但不能在上述流程中工作生成器,因为它正在查找具有该引用名称的文件或目录。
回答基本问题
首先,谈谈 真正的 问题,即尝试构建 Java 调用
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 }' <\"$d\"/status); uid=$(awk '/^Uid:/ { print }' <\"$d\"/status); pwent=$(getent passwd \"$uid\"); user=${pwent%%:*}; cmdline=$(shellQuoteNullSeparatedStream <\"$c\"); starttime=$(awk -v systick=\"$systick\" '{print int( / systick)}' \"$d\"/stat); uptime=$(awk '{print int()}' /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 }' <\"$d\"/status); uid=$(awk '/^Uid:/ { print }' <\"$d\"/status); pwent=$(getent passwd \"$uid\"); user=${pwent%%:*}; cmdline=$(shellQuoteNullSeparatedStream <\"$c\"); starttime=$(awk -v systick=\"$systick\" '{print int( / systick)}' \"$d\"/stat); uptime=$(awk '{print int()}' /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\""
)
您的 ProcessBuilder 无法启动进程,因为双引号不属于那里。命令行需要引号,以指示 docker info
是单个参数而不是两个参数。但是在没有命令行的情况下直接执行进程时,引号没有特殊含义。该参数已经是单个参数,只需将其作为单个字符串传递即可。
我想推荐一个替代方案。你不需要 bash 也不需要 grep。你有 Java。 Javasupports regular expressions就好了
所以,这里是没有 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
}
在 Charles Duffy extensively worked on this, whose findings are detailed
原因是当通过进程构建器执行时,没有对命令本身进行任何处理(通常由 sh
/bash
终端完成 运行 ).我掉进了这个兔子洞,因为在 Java.