【问题标题】:Passing a complex shell script via docker exec sh -c "..."通过 docker exec sh -c "..." 传递复杂的 shell 脚本
【发布时间】:2020-03-04 23:20:11
【问题描述】:

我有一个脚本可以在 linux 主机上的 sh 以及 alpine 容器内正常工作。但是,当我尝试使用 docker exec <containerID> sh -c "<script>" 执行该操作时,它行为不端。脚本的作用是输出类似ps的东西。

systick=$(getconf CLK_TCK); for c in /proc/*/cmdline; do d=$(dirname $c); name=$(grep Name: $d/status); pid=$(basename $d); uid=$(grep Uid: $d/status); uid=$(echo ${uid#Uid:} | xargs); uid=${uid%% *}; user=$(grep :$uid:[0-9] /etc/passwd); user=${user%%:*}; cmdline=$(cat $c|xargs -0 echo); starttime=$(($(awk '{print $22}' $d/stat) / systick)); uptime=$(awk '{print int($1)}' /proc/uptime); elapsed=$(($uptime-$starttime)); echo $pid $user $elapsed $cmdline; done

编辑:sh -c "<script>" 具有相同的行为。

【问题讨论】:

  • 当然,当您更明确地指出问题所在时。说脚本或命令行为不端可能是最模糊的。
  • @alex067 似乎并非如此。我已经给出了足以重现问题的一切。至于确切的失败 - 我没有说明它,因为消息在 shell 和操作系统之间有所不同。我尝试在 ubuntu 上使用 ubuntu 容器,在 ubuntu 上使用 alpine 容器。在高山本身等等。也得到了答案。不过感谢您的帮助!非常感谢

标签: docker sh docker-exec


【解决方案1】:

您无法从 docker exec 运行此脚本,因为变量将在发送到容器之前进行插值(即,您将从本地计算机获取值,而不是从容器内获取值)。

为了按您的意愿运行它,您需要在脚本中每次出现$ 时将$ 替换为\$

更好的方法是将脚本放入文件中,然后使用-v(即-v script.sh:/path/to/script.sh)将文件映射到容器内的某个位置,并通过docker exec /path/to/script.sh调用脚本

【讨论】:

    【解决方案2】:

    第 1 部分:可行的答案

    一个工作单线(引用供 Docker 使用)

    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'
    sh -c "$getProcessDataDef"  # or docker exec <container> sh -c "$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 "$@"
    

    单线衬里发生了什么

    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
    }
    

    单行代码使用的 Shell-Quoting Helper 发生了什么

    为了便于阅读和编辑,上面字符串化的函数如下所示:

    # This is the function we're including in our code passed to xargs in-band above:
    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'
    }
    

    第 2 部分:如何创建答案

    Python 有一个出色的 shlex.quote() 函数(或 Python 2 中的 pipes.quote()),可用于生成字符串的 shell 引用版本。在这种情况下,可以按如下方式使用:

    Python 3.7.6 (default, Feb 27 2020, 15:15:00)
    [Clang 7.1.0 (tags/RELEASE_710/final)] on darwin
    Type "help", "copyright", "credits" or "license" for more information.
    >>> s = r'''
    ... 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'
    ... }
    ... '''
    >>> import shlex
    >>> print(shlex.quote(s))
    '
    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'"'"'
    }
    '
    

    那个结果是 itself 在 shell 中一个完全有效的字符串。也就是说,可以跑:

    s='
    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'"'"'
    }
    '
    eval "$s"
    shellQuoteWords "hello world" 'hello world' "hello 'world'" 'hello "world"'
    

    ...并获得完全有效的输出。

    按照相同的过程生成一个字符串,该字符串评估为getProcessData 的定义。

    【讨论】:

    • @SamThomas, ...顺便说一句,我认为getent 将在您的目标系统上可用是正确的吗?如果没有,切换到user=$(id -u "$uid") 可能有意义。 (getent 虽然是 Linux 主义,但通常比直接阅读 /etc/passwd 更健壮,因为它甚至可以在具有不同 NSS 模块提供目录服务的系统上工作)。
    • 这是一个很好的观点,它就在那里,但有一个替代方案很好。感谢查尔斯度过了你的夜晚。
    • @SamThomas,别担心,这是一个有趣的谜题。顺便说一句,我想我从来没有兑现我在聊天中的承诺来描述cmdline=$(cat $c|xargs -0 echo) 是如何不忠的。最简单的例子可能是printf '%s\0' mv "hello world" "goodbye world" | xargs -0 echo——看看它是如何吐出mv hello world goodbye world的,它的作用与原来的mv "hello world" "goodbye world"命令完全不同。
    • 相比之下,使用此答案中的函数定义,printf '%s\0' mv "hello world" "goodbye world" | shellQuoteNullSeparatedStream 发出 'mv' 'hello world' 'goodbye world',它在运行原始命令时实际上会执行相同的操作。
    • 啊啊啊!!现在它很有意义
    猜你喜欢
    • 2019-10-16
    • 2015-12-20
    • 2022-01-07
    • 2017-03-14
    • 1970-01-01
    • 1970-01-01
    • 2023-03-11
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多