在这个简单的 Docker 包装器脚本示例中,如何正确传递包含空格的当前工作目录路径?
In this simple Docker wrapper script example, how may one correctly pass a current working directory path which contains spaces?
我的 Docker 包装器脚本在当前工作目录不包含空格时按预期工作,但是当它包含空格时会出现错误。
我已经简化了一个示例,以使用我能找到的最小官方 Docker 图像和一个众所周知的 GNU 核心实用程序。当然这个例子用处不大。在我的真实世界用例中,打包了一个复杂得多的环境。
Docker 包装脚本:
#!/usr/bin/env bash
##
## Dockerized ls
##
set -eux
# Only allocate tty if one is detected
# See
if [[ -t 0 ]]; then
DOCKER_RUN_OPTIONS+="-i "
fi
if [[ -t 1 ]]; then
DOCKER_RUN_OPTIONS+="-t "
fi
WORK_DIR="$(realpath .)"
DOCKER_RUN_OPTIONS+="--rm --user=$(id -u $(logname)):$(id -g $(logname)) --workdir=${WORK_DIR} --mount type=bind,source=${WORK_DIR},target=${WORK_DIR}"
exec docker run ${DOCKER_RUN_OPTIONS} busybox:latest ls "$@"
例如,您可以将其保存为 /tmp/docker_ls
。记得chmod +x /tmp/docker_ls
现在您可以在任何不包含空格的路径中使用此 Dockerized ls,如下所示:
/tmp/docker_ls -lah
/tmp/docker_ls -lah | grep 'r'
请注意,/tmp/docker_ls -lah /path/to/something
未实现。必须调整包装器脚本以解析参数并将路径参数装载到容器中。
你能看出为什么当当前工作目录路径包含空格时这不起作用吗?可以做些什么来纠正它?
解法:
@david-maze 的回答解决了问题。请参阅:
根据他的建议,我重构了我的脚本如下:
#!/usr/bin/env bash
##
## Dockerized ls
##
set -eux
# Only allocate tty if one is detected. See -
if [[ -t 0 ]]; then IT+=(-i); fi
if [[ -t 1 ]]; then IT+=(-t); fi
USER="$(id -u $(logname)):$(id -g $(logname))"
WORKDIR="$(realpath .)"
MOUNT="type=bind,source=${WORKDIR},target=${WORKDIR}"
exec docker run --rm "${IT[@]}" --user "${USER}" --workdir "${WORKDIR}" --mount "${MOUNT}" busybox:latest ls "$@"
我正在试一试,给你一些尝试:
改变这个:
DOCKER_RUN_OPTIONS+="--rm --user=$(id -u $(logname)):$(id -g $(logname)) --workdir=${WORK_DIR} --mount type=bind,source=${WORK_DIR},target=${WORK_DIR}"
为此:
DOCKER_RUN_OPTIONS+="--rm --user=$(id -u $(logname)):$(id -g $(logname)) --workdir=${WORK_DIR} --mount type=bind,source='${WORK_DIR}',target='${WORK_DIR}'"
本质上,当 $DOCKER_RUN_OPTIONS 变量在 'exec docker' 命令中被 bash 求值时,我们将 ' 放在那里以转义 space。
我还没有尝试过 - 这只是一种预感/第一次尝试。
如果您的目标是 运行 当前主机目录上的进程作为当前主机用户,您会发现使用主机进程比使用像 Docker 故意试图向你隐藏这些事情。对于您展示的内容,我将跳过 Docker 和 运行
#!/bin/sh
ls "$@"
大多数软件在没有 Docker 的情况下安装起来相当简单,要么使用像 APT 这样的包管理器,要么像 Python 的虚拟环境和 Node 的 node_modules
目录这样的文件系统级隔离。如果您正在编写此脚本,那么 Docker 只会妨碍您。
在可移植的 shell 脚本中,无法以保持单词冗长的方式制作“单词列表”。如果你知道你总是想传递一些麻烦的选项,那么这仍然相当简单:将它们直接包含在 docker run
命令中,不要尝试创建选项变量。
#!/bin/sh
RM_IT="--rm"
if [[ -t 0 ]]; then RM_IT="$RM_IT -i"; fi
if [[ -t 1 ]]; then RM_IT="$RM_IT -t"; fi
UID=$(id -u $(logname))
GID=$(id -g $(logname))
# We want the --rm -it options to be expanded into separate
# words; we want the volume options to stay as a single word
docker run $RM_IT "-u$UID:$GID" "-w$PWD" "-v$PWD:$PWD" \
busybox \
ls "$@"
有些 shell 像 ksh、bash 和 zsh 有数组类型,但这些 shell 可能不会出现在每个系统或环境中(你的 busybox 图像不例如,没有这些)。您还可以考虑选择一种更高级的脚本语言,它可以更明确地将单词传递到 exec
类型调用中。
我的 Docker 包装器脚本在当前工作目录不包含空格时按预期工作,但是当它包含空格时会出现错误。
我已经简化了一个示例,以使用我能找到的最小官方 Docker 图像和一个众所周知的 GNU 核心实用程序。当然这个例子用处不大。在我的真实世界用例中,打包了一个复杂得多的环境。
Docker 包装脚本:
#!/usr/bin/env bash
##
## Dockerized ls
##
set -eux
# Only allocate tty if one is detected
# See
if [[ -t 0 ]]; then
DOCKER_RUN_OPTIONS+="-i "
fi
if [[ -t 1 ]]; then
DOCKER_RUN_OPTIONS+="-t "
fi
WORK_DIR="$(realpath .)"
DOCKER_RUN_OPTIONS+="--rm --user=$(id -u $(logname)):$(id -g $(logname)) --workdir=${WORK_DIR} --mount type=bind,source=${WORK_DIR},target=${WORK_DIR}"
exec docker run ${DOCKER_RUN_OPTIONS} busybox:latest ls "$@"
例如,您可以将其保存为 /tmp/docker_ls
。记得chmod +x /tmp/docker_ls
现在您可以在任何不包含空格的路径中使用此 Dockerized ls,如下所示:
/tmp/docker_ls -lah
/tmp/docker_ls -lah | grep 'r'
请注意,/tmp/docker_ls -lah /path/to/something
未实现。必须调整包装器脚本以解析参数并将路径参数装载到容器中。
你能看出为什么当当前工作目录路径包含空格时这不起作用吗?可以做些什么来纠正它?
解法:
@david-maze 的回答解决了问题。请参阅:
根据他的建议,我重构了我的脚本如下:
#!/usr/bin/env bash
##
## Dockerized ls
##
set -eux
# Only allocate tty if one is detected. See -
if [[ -t 0 ]]; then IT+=(-i); fi
if [[ -t 1 ]]; then IT+=(-t); fi
USER="$(id -u $(logname)):$(id -g $(logname))"
WORKDIR="$(realpath .)"
MOUNT="type=bind,source=${WORKDIR},target=${WORKDIR}"
exec docker run --rm "${IT[@]}" --user "${USER}" --workdir "${WORKDIR}" --mount "${MOUNT}" busybox:latest ls "$@"
我正在试一试,给你一些尝试: 改变这个:
DOCKER_RUN_OPTIONS+="--rm --user=$(id -u $(logname)):$(id -g $(logname)) --workdir=${WORK_DIR} --mount type=bind,source=${WORK_DIR},target=${WORK_DIR}"
为此:
DOCKER_RUN_OPTIONS+="--rm --user=$(id -u $(logname)):$(id -g $(logname)) --workdir=${WORK_DIR} --mount type=bind,source='${WORK_DIR}',target='${WORK_DIR}'"
本质上,当 $DOCKER_RUN_OPTIONS 变量在 'exec docker' 命令中被 bash 求值时,我们将 ' 放在那里以转义 space。
我还没有尝试过 - 这只是一种预感/第一次尝试。
如果您的目标是 运行 当前主机目录上的进程作为当前主机用户,您会发现使用主机进程比使用像 Docker 故意试图向你隐藏这些事情。对于您展示的内容,我将跳过 Docker 和 运行
#!/bin/sh
ls "$@"
大多数软件在没有 Docker 的情况下安装起来相当简单,要么使用像 APT 这样的包管理器,要么像 Python 的虚拟环境和 Node 的 node_modules
目录这样的文件系统级隔离。如果您正在编写此脚本,那么 Docker 只会妨碍您。
在可移植的 shell 脚本中,无法以保持单词冗长的方式制作“单词列表”。如果你知道你总是想传递一些麻烦的选项,那么这仍然相当简单:将它们直接包含在 docker run
命令中,不要尝试创建选项变量。
#!/bin/sh
RM_IT="--rm"
if [[ -t 0 ]]; then RM_IT="$RM_IT -i"; fi
if [[ -t 1 ]]; then RM_IT="$RM_IT -t"; fi
UID=$(id -u $(logname))
GID=$(id -g $(logname))
# We want the --rm -it options to be expanded into separate
# words; we want the volume options to stay as a single word
docker run $RM_IT "-u$UID:$GID" "-w$PWD" "-v$PWD:$PWD" \
busybox \
ls "$@"
有些 shell 像 ksh、bash 和 zsh 有数组类型,但这些 shell 可能不会出现在每个系统或环境中(你的 busybox 图像不例如,没有这些)。您还可以考虑选择一种更高级的脚本语言,它可以更明确地将单词传递到 exec
类型调用中。