【问题标题】:Executing /usr/bin/env bash -c "command" in Java在 Java 中执行 /usr/bin/env bash -c "command"
【发布时间】:2020-03-17 23:53:03
【问题描述】:

如果我在终端中运行/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)"

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

【问题讨论】:

  • 也就是说,您还可以考虑尝试使System.out.println(your_java_string) 与命令完全匹配,其中your_java_stringProcessBuilder 初始化程序的第四个参数。只是这样做println 应该可以让您很好地了解自己做错了什么。
  • @CharlesDuffy 我已经明白你在说什么了。这就是我给出第二个命令的原因。它需要使用引号运行。我给出的命令是有效的。
  • @CharlesDuffy 我认为你错了,因为在引用的命令中,如果你希望 bash 解析 -c 中给出的内容,那么你需要转义 $ 否则它将被当前 shell 解析在发送到 bash 之前
  • 如果您以前的 cmets 不再相关,请删除它们,这样我们就不会被重定向到聊天。我只是问,在 ProcessBuilder 中,第一个 arg 应该是命令,因此将“bash”作为第二个不会将其设置为“.../env”的参数? stackoverflow.com/questions/11198678/…
  • 您可以在单个 shell 单词中的多种引用样式之间切换。如果您遇到将单引号与 awk 结合使用时遇到问题的特定情况,我很乐意为您提供帮助。

标签: java bash processbuilder


【解决方案1】:

您的 ProcessBuilder 未能启动进程,因为双引号不属于那里。命令行需要引号,以指示 docker info 是单个参数而不是两个参数。但是在没有命令行的情况下直接执行进程时,引号没有特殊含义。该参数已经是一个参数,只需将其作为单个字符串传递即可。

我想提出一个替代方案。你不需要 bash 也不需要 grep。你有Java。 Java supports 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
}

【讨论】:

    【解决方案2】:

    回答基本问题

    首先,谈到真正的问题,尝试构建调用This script won't work through docker exec 中引用的shell 脚本的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 $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\""
    )
    

    【讨论】:

      【解决方案3】:

      Charles Duffy 对此进行了广泛的研究之后,他的发现详细介绍了here 以及该答案中的链接帖子,我意识到我不必像我经常那样逃避嵌入式命令/脚本/替换通过脚本或终端执行相同的命令。

      原因是当通过进程构建器执行时,命令本身没有进行任何处理(这将始终由sh/bash 终端正在运行)。我掉进了这个兔子洞,因为在 Java 上测试之前,我要确保命令在终端中运行。

      【讨论】:

      • nod -- 或者,更一般地说,相同的值需要以不同的方式转义,以将其表示为不同语言的文字字符串。
      猜你喜欢
      • 2014-03-03
      • 2020-01-07
      • 1970-01-01
      • 2016-03-23
      • 2016-09-12
      • 2015-08-01
      • 2012-09-21
      • 1970-01-01
      • 2013-07-16
      相关资源
      最近更新 更多