如何通过docker run向Shell脚本传递参数

258

我是docker世界的新手。我需要通过一个docker容器调用一个带有命令行参数的shell脚本。 例如:我的shell脚本如下:

#!bin/bash
echo $1

Dockerfile 的样子是这样的:

FROM ubuntu:14.04
COPY ./file.sh /
CMD /bin/bash file.sh

我不确定如何在运行容器时传递参数

8个回答

302

使用这个脚本 file.sh

#!/bin/bash
echo Your container args are: "$@"

并且这个 Dockerfile

FROM ubuntu:14.04
COPY ./file.sh /
ENTRYPOINT ["/file.sh"]

您应该具备以下能力:

% docker build -t test .
% docker run test hello world
Your container args are: hello world

18
如果像我一样忘记在“/file.sh”周围加上引号,它就无法正常运行。 - kev
10
由于某些原因,这个命令不适用于 ENTRYPOINT ./file.sh - phil294
13
别忘了执行 chmod +x file.sh 命令,以设置文件的可执行标记。 - topskip
2
@kev,你知道为什么会这样吗?["/file.sh"]/file.sh甚至[/file.sh]之间有什么区别? - Nitzan
3
@Nitzankin,请查看我的答案,了解为什么需要正确的JSON格式。 - BMitch
显示剩余2条评论

113

使用相同的file.sh

#!/bin/bash
echo $1

使用现有的Dockerfile构建镜像:
docker build -t test .

使用参数abcxyz或其他内容运行该图像。

docker run -ti --rm test /file.sh abc

docker run -ti --rm test /file.sh xyz

55
如果您不希望最终用户直接知道file.sh,我认为ENTRYPOINT是可行的选择。 - Grae Kindel
你怎么能像这样启动一个脚本 docker run -ti test /file.sh abc。我感觉脚本不会运行,因为应该是 docker run -ti test sh /file.sh abc。sh或/bin/sh才能正确运行它。 - Vamsidhar Muggulla
1
对于其他人来说,/usr/bin/env 技巧是可选的样式偏好,而不是使其工作的要求。另外,#! 行指示默认使用哪个解释器。因此,只需调用脚本即可运行。 - wheredidthatnamecomefrom

81
这里有几个相互作用的事情:
  1. docker run your_image arg1 arg2会用arg1 arg2替换CMD的值。这是对CMD的完全替换,而不是追加更多的值。这就是为什么你经常看到docker run some_image /bin/bash在容器中运行bash shell。

  2. 当你同时定义了ENTRYPOINT和CMD的值时,Docker会将两者连接起来并运行这个连接后的命令。所以如果你将entrypoint定义为file.sh,现在你可以通过附加参数来运行容器,这些参数将作为参数传递给file.sh

  3. Docker中的Entrypoints和Commands有两种语法,一种是字符串语法,它将启动一个shell,另一种是json语法,它将执行一个exec。Shell对于处理IO重定向、将多个命令链接在一起(使用&&等)以及变量替换等非常有用。然而,这个shell会妨碍信号处理(如果你曾经看到停止容器需要10秒的延迟,通常就是这个原因)以及连接entrypoint和command在一起。如果你将entrypoint定义为一个字符串,它将运行/bin/sh -c "file.sh",这本身没问题。但是如果你也将command定义为一个字符串,你会看到类似/bin/sh -c "file.sh" /bin/sh -c "arg1 arg2"的命令在容器内部被启动,这不太好。请参考这里的表格以了解更多关于这两个选项如何相互作用的信息

  4. shell的-c选项只接受一个参数。之后的所有内容都将作为$1$2等传递给这个单一的参数,但不会传递给嵌入的shell脚本,除非你显式地传递这些参数。例如,/bin/sh -c "file.sh $1 $2" "arg1" "arg2"可以工作,但/bin/sh -c "file.sh" "arg1" "arg2"不行,因为file.sh将被调用而没有参数。

把所有这些放在一起,通常的设计是:

FROM ubuntu:14.04
COPY ./file.sh /
RUN chmod 755 /file.sh
# Note the json syntax on this next line is strict, double quotes, and any syntax
# error will result in a shell being used to run the line.
ENTRYPOINT [ "/file.sh" ]

然后你用以下方式运行它:
docker run your_image arg1 arg2

这个问题有更多详细的内容,请参考以下链接:

2
我曾尝试将我的入口点设置为["bash", "--login", "-c"],以获取镜像中的/etc/profile源文件,但后来我想知道为什么传递给docker run的shell脚本不会传递任何参数...你的答案解决了我的疑惑,谢谢! - Apteryx
1
不应该是ENTRYPOINT ["/file.sh"]吗?即前面要加上 / 吗? - Dave

70
使用 Docker,传递此类信息的正确方式是通过环境变量。因此,在相同的 Dockerfile 中,将脚本更改为:
#!/bin/bash
echo $FOO

构建完成后,请使用以下 Docker 命令:

docker run -e FOO="hello world!" test

46
为什么这是最受欢迎的答案?环境变量是传递信息的另一种方式,但不是OP所问的。当然,OP希望将参数传递给容器没有任何不妥之处。 - Partly Cloudy
8
在 Stack Overflow 上有很多关于 XY 问题的提问。由于提问者表示他们是 Docker 的新手,因此展示建议的实现目标的方式作为答案是完全合理的。这使得这个答案非常好。 - colm.anseo
2
最简单的方法是将变量作为环境变量传递,而不是作为构建参数传递,这样可以轻松完成工作,避免繁琐的操作。特别适用于传递机密信息。 - Arvind Sridharan
1
我遇到了一个棘手的情况,需要将一个带有空格的参数传递到入口点内部的命令中。 pytest -m“not longest” 这是唯一对我有效的解决方案。无论是否“正确”,我很高兴它能用在这里。 - mildewey

30

我手头有一个可以真正运行代码的脚本文件。这个脚本文件可能相对较为复杂,我们称之为“run_container”。这个脚本从命令行中获取参数:

run_container p1 p2 p3

一个简单的 run_container 可能是:

#!/bin/bash
echo "argc = ${#*}"
echo "argv = ${*}"

我想做的是,在“装配”后,我希望能够通过Docker命令行参数启动此容器,如下所示:

docker run image_name p1 p2 p3

并且需要使用p1、p2、p3作为参数来运行run_container脚本。

这是我的解决方案:

Dockerfile:

FROM docker.io/ubuntu
ADD run_container /
ENTRYPOINT ["/bin/bash", "-c", "/run_container \"$@\"", "--"]

8
ENTRYPOINT 数组中的第三个值替换为 "/run_container \"$@\"" 可以正确处理包含空格的参数(例如,docker run image_name foo 'bar baz' quux)。 - davidchambers
在我的bash文件中添加switch/case语句后,ENTRYPOINT["run_container.sh"]不再适用于我,但是ENTRYPOINT["sh", "-c", "run_container.sh"]将不再接受我的参数。这个解决方案(使用@davidchambers的建议)对我有用。 - rhamilton

11

如果您想在构建时运行它:

CMD /bin/bash /file.sh arg1

如果你想在运行时运行它:

ENTRYPOINT ["/bin/bash"]
CMD ["/file.sh", "arg1"]

然后在主机 shell 中执行

docker build -t test .
docker run -i -t test

3
ENTRYPOINT 对于我认为想要运行时的 OP 来说是一个很好的答案,但如果你真的想要构建时变量,那么这个答案就是错误的。请使用 ARGdocker build --build-arg https://docs.docker.com/engine/reference/builder/#arg - Grae Kindel

3

我想使用 ENTRYPOINT 的字符串版本,这样我就可以使用交互式 shell。

FROM docker.io/ubuntu
...
ENTRYPOINT python -m server "$@"

然后运行的命令(注意--):

docker run -it server -- --my_server_flag

这是这样工作的: ENTRYPOINT 的字符串版本运行一个 shell,该 shell 使用 -c 标志指定的命令运行。在 -- 之后传递给 shell 的参数将作为命令的参数提供,其中 "$@" 位于该命令中。请参见此处的表格:https://tldp.org/LDP/abs/html/options.html。(感谢 @jkh 和 @BMitch 的答案帮助我理解发生了什么。)

-1

另一个选项...

使其工作的方法是

docker run -d --rm $IMG_NAME "bash:command1&&command2&&command3"

在 Dockerfile 中

ENTRYPOINT ["/entrypoint.sh"]

在 entrypoint.sh 中

#!/bin/sh

entrypoint_params=$1
printf "==>[entrypoint.sh] %s\n" "entry_point_param is $entrypoint_params"

PARAM1=$(echo $entrypoint_params | cut -d':' -f1) # output is 1 must be 'bash' it     will be tested    
PARAM2=$(echo $entrypoint_params | cut -d':' -f2) # the real command separated by     &&

printf "==>[entrypoint.sh] %s\n" "PARAM1=$PARAM1"
printf "==>[entrypoint.sh] %s\n" "PARAM2=$PARAM2"

if [ "$PARAM1" = "bash" ];
then
    printf "==>[entrypoint.sh] %s\n" "about to running $PARAM2 command"
    echo $PARAM2 | tr '&&' '\n' | while read cmd; do
        $cmd
    done    
fi

一些注释和限制...带有“:”的命令需要在cut -d':'中进行更改,像docker run -d --rm $IMG_NAME "bash:echo $PATH"这样的命令将显示主机路径值而不是主机本身。 - wagnermarques

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接