簡體   English   中英

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

如果沒有引號,命令將不會在終端上運行,也不會在我上面的流程構建器中運行(出於同樣的原因),但是使用引號它可以在終端中運行,但不能在上述流程構建器中運行,因為它正在查找文件或該引用名稱的目錄。

回答根本問題

首先,談到真正的問題,嘗試構建調用此腳本中引用的 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\""
)

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

Charles Duffy對此進行了廣泛的研究之后,他的研究結果在此處以及該答案中的鏈接帖子中有詳細說明,我意識到我不必像定期對通過執行的相同命令那樣逃避嵌入式命令/腳本/替換腳本或終端。

原因是當通過進程構建器執行時,沒有對命令本身進行處理(這總是由終端運行的sh / bash完成)。 我掉進了這個兔子洞,因為在 Java 上測試它之前,我首先要確保該命令在終端中工作。

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM