从 bash shell 中将变量传递给 docker run,出现“未终止的引用字符串”错误

从 bash shell 中将变量传递给 docker run,出现“未终止的引用字符串”错误

我有一个 bash shell,其中包含多个变量,这些变量构成了 docker run 命令的命令选项。我怀疑某个<字符破坏了我传递的配置行。

这是我想要传递的命令:

docker run -it --entrypoint /bin/sh alpine -c \
"apk update && apk add application && application -f < /dir/file.log"

当我在命令行中运行它时,它运行良好。如果我在脚本中以该行运行它,它运行良好。但是当它在带有变量替换的脚本中运行时,它会出现以下错误:

update: line 1: syntax error: unterminated quoted string

这是我的实际脚本:(它可能不是最佳实践,但我很难将多个命令传递给入口点命令,因此对其进行了破解:

DOCKER_SHELL=/bin/sh
DOCKER_IMAGE=alpine
TEMP=/tmp
FILE=file.log
DOCKER_ENTRY="apk update && apk add application && application -f < "$TEMP"/"$FILE
TEST=\"$DOCKER_ENTRY\"

docker run -it --entrypoint $DOCKER_SHELL $DOCKER_IMAGE -c $TEST

如果字符串中$DOCKER_ENTRY没有字符,我可以正常运行变量。所以我想我很难在传递的行中“转义”字符。我确实必须将空行作为带引号的行传递,所以我遇到的挑战要么是我的命令必须更改,要么我需要做其他事情才能使其工作。<<--entrypoint

有什么想法吗?

注意:

  1. 虽然我知道可以使用 Dockerfile 来执行此操作,但我们的目的是使用上述 bash 文件中的一行来执行此操作。
  2. 我绝不是 Docker 专家,因此如果有更好的运行命令的方法--entrypoint,也请指出。

答案1

一般信息

代码中的引用非常错误。重要信息:

  1. 未加引号的变量进行单词拆分和文件名生成
  2. 从变量扩展中出现的引号对于扩展变量的 shell 来说并不特殊。
  3. 有两个 shell:解释脚本的 shell 和在容器中运行的 shell。每个 shell 都会自行解析代码。

另外考虑小写变量名。如果您习惯使用大写名称,那么迟早您会不经意地更改、、、PATH或任何IFS类似的东西,然后就会出现问题。USERTERMHOME


具体分析

在这一行中:

DOCKER_ENTRY="apk update && apk add application && application -f < "$TEMP"/"$FILE

$TEMP并且$FILE没有被引号括起来。它们在脚本中的特定值是“安全的”,因此没有引号不会对你造成影响在这种情况下(除非IFS环境是“不幸的”)。但是,如果您将来将它们更改为“不安全”,该怎么办?您会记得修复代码吗?无论值是什么,正确的做法都是使用双引号。

然后这里:

TEST=\"$DOCKER_ENTRY\"

变量没有被引用。这是一个案例不引用不是一个错误(但在我看来,始终使用引号比记住所有可以安全省略引号的极端情况更容易。)转义的双引号字符并不特殊,并存储在变量中TEST

和这里:

docker run -it --entrypoint $DOCKER_SHELL $DOCKER_IMAGE -c $TEST

没有变量被引用。与 和 类似,$TEMP和的$FILE值是“安全的”(除非……)。然而,你对 所做的操作是致命的。变量扩展后,该命令将相当于:$DOCKER_SHELL$DOCKER_IMAGEIFS$TEST

docker run -it --entrypoint /bin/sh alpine -c \"apk update && apk add application && application -f < /tmp/file.log\"

其中,扩展中的双引号$TEST不是特殊的,因此我将它们显示为\"好像我在 shell 中输入非特殊的双引号一样。单词拆分(因为未引用 $TEST) 构成"apk一个单独的词,update另一个单独的词,等等。

容器内部将使用/bin/sh以下参数运行:-c,,,,,,,,,,,,,。"apk​​​​update&&apkaddapplication&&application-f</tmp/file.log"

的选项参数-c是。这是容器内"apk唯一会尝试运行的代码。该代码包含未终止的引号字符串。工作原理类似于此问题中的第二个:/bin/shupdatesh中的第二个 sh 是什么sh -c 'some shell code' sh这就是为什么你得到:

update: line 1: syntax error: unterminated quoted string

解决方案

此代码片段修复了引用,引入了小写的变量名和舍邦

#!/bin/sh

docker_shell=/bin/sh
docker_image=alpine
temp=/tmp
file=file.log
docker_entry="apk update && apk add application && application -f < $temp/$file"

docker run -it --entrypoint "$docker_shell" "$docker_image" -c "$docker_entry"

它可以改进,我们稍后会改进它。目前最重要的是双引号$docker_entry扩展为一个单词,因此在变量扩展后,最后一行将相当于:

docker run -it --entrypoint /bin/sh alpine -c 'apk update && apk add application && application -f < /tmp/file.log'

其中我使用单引号来表示扩展中的单个单词$docker_entry,就像我在 shell 中打字时试图传递单个单词一样。

现在容器内部/bin/sh将使用以下参数运行:-capk update && apk add application && application -f < /tmp/file.log

该脚本仍然有些缺陷。如果您将 更改file=file.log为 egfile=file'.logfile='file.log; do_unwanted_things',则从 的扩展中$docker_entry您将分别获得… < /tmp/file'.log… < /tmp/file.log; do_unwanted_things。这将被解释为sh容器内部的 shell 代码。单引号将是一个错误,但相对无害;do_unwanted_things代表您将意外运行的任意代码,一般来说它远非无害。

注意file'.logfile.log; do_unwanted_things是合法的名称。您可能希望使用其中任何一个或任何名称作为名称。要在上面的脚本中使用它们,您需要在传递给 的 shell 代码中进行额外的转义或引用sh。在代码中嵌入引号,如下所示:

docker_entry="apk update && apk add application && application -f < '$temp'/'$file'"

(看引号中的引号) 不是一种强大的方法,因为单引号(如file'.log)会干扰、破坏事物并为注入代码提供可能性(例如do_unwanted_things)。

您可以手动更改原始变量,以便它以后表现良好。例如:

file='file.log\; do_unwanted_things'

如果您的$temp$file是预先知道的并且是静态的,那么您总是可以找到这样的引用/转义(在tempfile和/或内部docker_entry),这样它就会表现良好,不需要的东西就不会被视为代码。如果您想可靠地支持任何值而不每次都手动调整代码(因此可能是事先不知道的值),那就没那么简单了。

一般来说,如果您可以使用任何名称而不必担心其中的某些部分会被解释为代码(例如有意义的引用或要运行的实际命令),那就更好了。

在 Bash 中,你可以尝试使用${temp@Q}${file@Q}Q运算符使值以可以重用为要解析的 shell 代码的格式引用,因此解析后,您将获得原始值。如果您必须在 shell 代码中重用变量(例如,ssh user@server code没有其他简单的方法可以传递变量,只能在内部传递code),这将非常有用。我们将回到这种方法。

假设sh -c我们不需要传递$temp$file作为代码。 Shell 提供了一种将数据作为参数传递的强大方法:

#!/bin/sh

docker_shell=/bin/sh
docker_image=alpine
temp=/tmp
file=file.log
docker_entry='apk update && apk add application && application -f < "$1/$2"'

docker run -it --entrypoint "$docker_shell" "$docker_image" -c "$docker_entry" sh "$temp" "$file"

$temp现在,容器中的 shell 将识别本地扩展的值$1;而扩展的值$file将称为$2docker_entry将包含"$1/$2"文字字符串。$1$2将在容器中扩展。在容器中运行的 shell 代码中被正确双引号括起来。所有这些确保(或)$1/$2的值永远不会被视为 shell 代码。$temp$file

您不必在容器 () 中连接$1和,而是可以在脚本中连接并形成单个参数:;然后将其引用为。如下所示:$2$1/$2$temp/$file$1


docker_entry='apk update && apk add application && application -f < "$1"'

docker run -it --entrypoint "$docker_shell" "$docker_image" -c "$docker_entry" sh "$temp/$file"

这是一种外观上的改变。

无论您选择将$temp$file作为两个参数还是$temp/$file一个参数传递,代码都应该是完全安全的。我看到的唯一缺点是docker_entry包含$1/$2$1不具有描述性。在定义变量的地方,不清楚这些位置参数(将)意味着什么。因此,请考虑以下有点复杂的示例,但在您了解其要点后,您可能会喜欢它,尤其是在纯中sh

#!/bin/sh

docker_shell=/bin/sh
docker_image=alpine
temp=/tmp
file=file.log
docker_entry='apk update && apk add application && application -f < "$temp/$file"'

docker run -it --entrypoint "$docker_shell" "$docker_image" -c \
'temp="$1"; file="$2"; '"$docker_entry" sh "$temp" "$file"

现在docker_entry包含有意义的变量名(注意:它实际上包含名称,而不是值)。该行在容器中docker …附加了 shell 代码(temp="$1"; file="$2";),并从作为后续参数提供的局部变量中创建变量。这样,当容器中的 shell 从扩展中获取代码时$docker_entry,变量就会在那里。

使用 (本地) Bash,您可以使用有意义的名称,而无需额外的 shell 代码。使用已经提到的Q运算符:

#!/bin/bash

docker_shell=/bin/sh
docker_image=alpine
temp=/tmp
file=file.log
docker_entry="apk update && apk add application && application -f < ${temp@Q}/${file@Q}"

docker run -it --entrypoint "$docker_shell" "$docker_image" -c "$docker_entry"

现在包含和docker_entry的值,而不是上例中的文字字符串和(但是在脚本本身中,您会在两种情况下看到有意义的名称,不是和 之类的)。这些值不一定是原始值;如果需要,它们可以由操作员修改,因此当嵌入到这样的 shell 代码中时,它们始终是安全的。$temp$file$temp$file$1Q

docker_entry包含值的操作类似于您尝试执行的操作;也类似于此答案中第一个固定脚本的操作。改进之处在于:任何值都将被稳固安全地@Q处理。 ……嗯,不幸的是,它仍然取决于变量的值,还取决于容器内部的实现。问题是对于变量中的某些字符(换行符、制表符),Bash 使用shANSI-C 引用(至少 Bash 5.2.15 是这样做的),因此显然它希望其他 shell 能够理解这种类型的引用;但是的某些实现sh无法理解。sh容器中的可能理解也可能不理解。例如,shlinked tobash理解语法,shlinked todash则不理解。


最后说明

我不太了解 Docker。我不知道“是否有更好的方法来运行命令--entrypoint”。我的回答根本没有解决这个问题。

相关内容