我有一个 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
有什么想法吗?
注意:
- 虽然我知道可以使用 Dockerfile 来执行此操作,但我们的目的是使用上述 bash 文件中的一行来执行此操作。
- 我绝不是 Docker 专家,因此如果有更好的运行命令的方法
--entrypoint
,也请指出。
答案1
一般信息
代码中的引用非常错误。重要信息:
- 未加引号的变量进行单词拆分和文件名生成。
- 从变量扩展中出现的引号对于扩展变量的 shell 来说并不特殊。
- 有两个 shell:解释脚本的 shell 和在容器中运行的 shell。每个 shell 都会自行解析代码。
另外考虑小写变量名。如果您习惯使用大写名称,那么迟早您会不经意地更改、、、PATH
或任何IFS
类似的东西,然后就会出现问题。USER
TERM
HOME
具体分析
在这一行中:
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_IMAGE
IFS
$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
&&
apk
add
application
&&
application
-f
<
/tmp/file.log"
的选项参数-c
是。这是容器内"apk
唯一会尝试运行的代码。该代码包含未终止的引号字符串。工作原理类似于此问题中的第二个:/bin/sh
update
sh
中的第二个 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
将使用以下参数运行:-c
,apk update && apk add application && application -f < /tmp/file.log
。
该脚本仍然有些缺陷。如果您将 更改file=file.log
为 egfile=file'.log
或file='file.log; do_unwanted_things'
,则从 的扩展中$docker_entry
您将分别获得… < /tmp/file'.log
或… < /tmp/file.log; do_unwanted_things
。这将被解释为sh
容器内部的 shell 代码。单引号将是一个错误,但相对无害;do_unwanted_things
代表您将意外运行的任意代码,一般来说它远非无害。
注意file'.log
和file.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
是预先知道的并且是静态的,那么您总是可以找到这样的引用/转义(在temp
、file
和/或内部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
将称为$2
。docker_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
$1
Q
docker_entry
包含值的操作类似于您尝试执行的操作;也类似于此答案中第一个固定脚本的操作。改进之处在于:任何值都将被稳固安全地@Q
处理。 ……嗯,不幸的是,它仍然取决于变量的值,还取决于容器内部的实现。问题是对于变量中的某些字符(换行符、制表符),Bash 使用sh
ANSI-C 引用(至少 Bash 5.2.15 是这样做的),因此显然它希望其他 shell 能够理解这种类型的引用;但是的某些实现sh
无法理解。sh
容器中的可能理解也可能不理解。例如,sh
linked tobash
理解语法,sh
linked todash
则不理解。
最后说明
我不太了解 Docker。我不知道“是否有更好的方法来运行命令--entrypoint
”。我的回答根本没有解决这个问题。