这个答案可能仍然不完整,但对这个问题很着迷,我花了一些时间使用以下 Dockerfile 对其进行调试:
FROM debian:10
SHELL ["/bin/sh", "-ec"]
RUN apt-get update > /dev/null; apt-get -y install psmisc > /dev/null
# First layer is good: expected 1, got 1
RUN /bin/bash -exc "pstree; echo SHLVL=\$SHLVL"
# The surprising example (getting the escaping right is already tricky)
RUN /bin/bash -exc "/bin/bash -exc \"pstree; echo SHLVL=\\\$SHLVL\"" # expected 2, got 1
RUN /bin/bash -exc "/bin/bash -exc \"/bin/bash -exc \\\"pstree; echo SHLVL=\\\\\\\$SHLVL\\\"\"" # expected 3, got 1
# Now what happens if two commands run in the inner bash
RUN /bin/bash -exc ":; /bin/bash -exc \":; pstree; echo SHLVL=\\\$SHLVL\"" # expected 2, got 1
RUN /bin/bash -exc ":; /bin/bash -exc \":; /bin/bash -exc \\\":; pstree; echo SHLVL=\\\\\\\$SHLVL\\\"\"" # expected 3, got 1
有趣的事情似乎是:如果bash 调用之后紧跟着另一个,它将被“优化”(?)。据我所知,这不是 Docker 特有的东西,因为它可以交互地重现(在我的 Debian 10 系统上,问题中的交互式命令序列产生 1、2、2 而不是 1、2、3嵌套!)。
无论如何,构建 Dockerfile 的输出如下:
Sending build context to Docker daemon 30.21kB
Step 1/8 : FROM debian:10
---> 8e9f8546050d
Step 2/8 : SHELL ["/bin/sh", "-ec"]
---> Running in 3509ef249c45
Removing intermediate container 3509ef249c45
---> 8956c1fddb7c
Step 3/8 : RUN apt-get update > /dev/null; apt-get -y install psmisc > /dev/null
---> Running in 5cabec19144a
debconf: delaying package configuration, since apt-utils is not installed
Removing intermediate container 5cabec19144a
---> 64cad97f7793
Step 4/8 : RUN /bin/bash -exc "pstree; echo SHLVL=\$SHLVL"
---> Running in 22a0aa663163
+ pstree
sh---bash---pstree
+ echo SHLVL=1
SHLVL=1
Removing intermediate container 22a0aa663163
---> 4caa146b24f6
Step 5/8 : RUN /bin/bash -exc "/bin/bash -exc \"pstree; echo SHLVL=\\\$SHLVL\"" # expected 2, got 1
---> Running in 538ff45db230
+ /bin/bash -exc 'pstree; echo SHLVL=$SHLVL'
+ pstree
sh---bash---pstree
SHLVL=1
+ echo SHLVL=1
Removing intermediate container 538ff45db230
---> 1d4c9c2638fa
Step 6/8 : RUN /bin/bash -exc "/bin/bash -exc \"/bin/bash -exc \\\"pstree; echo SHLVL=\\\\\\\$SHLVL\\\"\"" # expected 3, got 1
---> Running in 3f0650d4d21b
+ /bin/bash -exc '/bin/bash -exc "pstree; echo SHLVL=\$SHLVL"'
+ /bin/bash -exc 'pstree; echo SHLVL=$SHLVL'
+ pstree
sh---bash---pstree
SHLVL=1
+ echo SHLVL=1
Removing intermediate container 3f0650d4d21b
---> 2d977033884d
Step 7/8 : RUN /bin/bash -exc ":; /bin/bash -exc \":; pstree; echo SHLVL=\\\$SHLVL\"" # expected 2, got 1
---> Running in 39b79af0f558
+ :
+ /bin/bash -exc ':; pstree; echo SHLVL=$SHLVL'
+ :
+ pstree
sh---bash---bash---pstree
+ echo SHLVL=2
SHLVL=2
Removing intermediate container 39b79af0f558
---> 48170e9bcb01
Step 8/8 : RUN /bin/bash -exc ":; /bin/bash -exc \":; /bin/bash -exc \\\":; pstree; echo SHLVL=\\\\\\\$SHLVL\\\"\"" # expected 3, got 1
---> Running in 456e6ec421ca
+ :
+ /bin/bash -exc ':; /bin/bash -exc ":; pstree; echo SHLVL=\$SHLVL"'
+ :
+ /bin/bash -exc ':; pstree; echo SHLVL=$SHLVL'
+ :
+ pstree
sh---bash---bash---bash---pstree
+ echo SHLVL=3
SHLVL=3
Removing intermediate container 456e6ec421ca
---> 30a07d3bdc95
Successfully built 30a07d3bdc95
Successfully tagged test:latest
最后,pstree 的输出很有趣,因为它显示了在各个点实际上没有其他 bash 进程在运行(即变量在所有情况下都正确跟踪实际的 shell 嵌套,只是有时存在运行的 shell 比预期的少)。