Docker中终端显示的行数在Docker容器内部发生变化。

42

我想知道如何改变下面的行为。假设我的终端有28行。然后我使用以下命令:

$ tput lines # my terminal
28
$ docker run  --rm  -it ubuntu:16.04 tput lines  # docker container
24  ## WHY??
$ docker run  --rm  -it ubuntu:16.04 bash # docker container inside command
root@810effa2777c:/# tput lines
28

从结果来看,即使所有的结果都应该是28,但当我调用容器时docker run --rm -it ubuntu:16.04 tput lines它总是给出24,尽管我的终端大小已经改变。这不仅发生在Ubuntu容器中,我还试过Debian (docker run --rm -it debian tput lines),结果也是一样的24。

目的是使用mdp演示工具,该工具考虑到你的终端行数。当我的实现失败时,我尝试了其他人的docker实现,但遇到了同样的错误。

这是我的错误截图:

Docker number of lines in terminal changing inside docker

有没有人知道可能是什么原因以及如何解决?

6个回答

45

更新

现在你可以使用以下命令安装goinside命令行工具:

sudo npm install -g goinside

使用以下命令进入具有适当终端大小的 Docker 容器:

goinside docker_container_name

goinside的逻辑

感谢@VonC的回答,我们得到了一个解决这个问题的简单bash代码片段,并将其放置在~/.profile中:

goinside(){
    docker exec -it $1 bash -c "stty cols $COLUMNS rows $LINES && bash";
}
export -f goinside

现在你可以通过以下命令在 Docker 容器内部,而不会遇到终端大小的问题:

$ goinside containername


请记得在使用 goinside 函数之前运行 source ~/.profile


启用 Bash 自动补全

(正如下面的评论中提到的那样) 如果你想要为 goinside 启用自动补全,可以在 .profile 中使用以下代码片段:

goinside(){
    docker exec -it $1 bash -c "stty cols $COLUMNS rows $LINES && bash";
}
_goinside(){
    COMPREPLY=( $(docker ps --format "{{.Names}}" -f name=$2) );
}
complete -F _goinside goinside;
export -f goinside;

启用zsh的自动补全功能

如果您将zsh作为默认终端,可以在您的~/.zshrc文件中使用以下代码片段:

autoload bashcompinit
bashcompinit
goinside(){
    docker exec -it $1 bash -c "stty cols $COLUMNS rows $LINES && bash";
}
_goinside(){
    COMPREPLY=( $(docker ps --format "{{.Names}}" -f name=$2) );
}
complete -F _goinside goinside;
export goinside;

2
同时,您可以进行自动完成。 _goinside(){ COMPREPLY=( $(docker ps --format "{{.Names}}" -f name=$2) ) } 然后导出它 complete -F _goinside goinsid - Shiplu Mokaddim

19

2018年9月更新:检查docker 18.06是否存在相同问题(不应该有, 在moby/moby问题33794之后,还有moby/moby问题35407PR 37172, 是18.06版本说明的一部分)。


2016年:

Ubuntu Dockerfile包括:

CMD ["/bin/bash"]

这意味着默认的ENTRYPOINTsh -c(我怀疑在sh会话中tput line无法正常工作,因为tput使用terminfo数据库,该数据库可能仅针对该镜像中的bash设置)。您可以尝试用bash -c覆盖ENTRYPOINT并检查是否效果更好。但是从命令行不起作用:
docker run --entrypoint /bin/bash --rm  -it ubuntu:16.04 -i -c 'tput lines'
24

我会检查定义自定义图像选项。
FROM ubuntu:16.04
ENTRYPOINT ["/bin/bash", "-c"]

结果是相同的,虽然:
docker run --rm  -it u 'tput lines'
24

然而这个“有效”:

FROM ubuntu:16.04
ENTRYPOINT [ "/bin/bash" ]

使用:

docker@default:/c/Users/vonc/prog/testsu$ docker run --rm  -it u -i -c 'ls; tput lines'
bin  boot  dev  etc  home  lib  lib64  media  mnt  opt  proc  root  run  sbin  srv  sys  tmp  usr  var
48

可能存在同步问题,因为相同的命令有时会返回24。

实际上,以下内容始终使用“not 24”:

FROM ubuntu:16.04
ENTRYPOINT [ "/bin/bash", "-l", "-i", "-c" ]

docker run --rm  -it u -c 'sleep 0.1; ls; tput lines'
48

OP silgon 提出了评论中的提议

docker run --rm -it --entrypoint /bin/bash ubuntu:16.04 -c "sleep 0.1 && tput lines"

正如BMitch下面的评论中所说:

考虑到睡眠的成功,我怀疑docker会使用正在运行的命令来启动容器,一旦启动,客户端就会连接到正在运行的容器。通常只需要几毫秒。

这给了我另一个想法:

docker@default:/c/Users/vonc/prog/testsu$ 
docker run --entrypoint='/bin/bash' --name ub -d -it ubuntu:16.04
  0d9b8783afbb5e3ff4232da071d3f357985351ea1ce4d142bf6617ac456fb76b
docker@default:/c/Users/vonc/prog/testsu$ 
d attach ub
  root@0d9b8783afbb:/# tput lines
  48
  root@0d9b8783afbb:/# exit
exit
docker@default:/c/Users/vonc/prog/testsu$ drmae
0d9b8783afbb5e3ff4232da071d3f357985351ea1ce4d142bf6617ac456fb76b

tput lines在已连接的会话中运行得很好。
(关于drmae别名,请参见“如何删除旧的和未使用的Docker镜像”)


thajeztah在评论中添加内容

容器被创建后,使用默认设置(80x24)启动,之后(当使用-it参数),会附加一个会话。
会话指定了终端的大小;

请查看 "调整容器 TTY 大小" API。

 DEBU[0244] Calling POST /v1.25/containers/c42fd5c4eb79c06fd7f9912b8359022f7d93887afbb33b57a67ed8bb7bfee4‌​3a/resize?h=46&w=221 

更多信息请参见docker问题25450
这与问题10341“容器创建或启动应接受高度/宽度参数”有关。Aleksa Sarai (cyphar)2016年9月)表示:

这个问题实际上又在运行时规范(opencontainers/runtime-spec PR 563)中出现了。
基本上,由于Windows需要在第一次启动时设置控制台大小,我们可能会为所有平台添加此功能


OP silgon指向api/client/container/run.go中的代码:

// Telling the Windows daemon the initial size of the tty during start makes
// a far better user experience rather than relying on subsequent resizes
// to cause things to catch up.
if runtime.GOOS == "windows" {
    hostConfig.ConsoleSize[0], hostConfig.ConsoleSize[1] = dockerCli.GetTtySize()
}

针对这个逻辑问题:

在Linux上使用该属性并使用该值设置初始控制台大小是否有意义?

Kenfe-Mickaël Laventure (mlaventure)正在处理,新的补丁可能会出现在Docker 1.13中。


@silgon 看起来存在同步问题:尝试多次执行我在答案中提到的最后一个命令(已编辑):大多数情况下,它不会返回24。 - VonC
2
@silgon 实际上,如果在tput命令前加上sleep,结果永远不会是24。 - VonC
1
鉴于sleep的成功,我怀疑Docker会使用正在运行的命令来启动容器,一旦启动完成,客户端就会连接到正在运行的容器上。通常这个过程只需要几毫秒的时间。 - BMitch
试一下吧,非常完美,谢谢!现在我知道如何为其他应用程序解决问题了。你能把命令改成 docker run --rm -it --entrypoint /bin/bash ubuntu:16.04 -c "sleep 0.1 && tput lines" 吗?这样它就是一个通用的答案,不会依赖于你创建的容器。我已经接受了你的答案。 =)另外,@VonC,根据系统提示,我仍然需要等待18小时才能授予您奖励。我尝试了但它没有让我操作。再次感谢!;) - silgon
1
我认为 @vonc 的结论是正确的;容器被 创建,然后使用默认值(80x24)启动,之后(当使用 -it 时),会连接一个会话。该 会话 指定了终端的大小;DEBU[0244] 调用 POST /v1.25/containers/c42fd5c4eb79c06fd7f9912b8359022f7d93887afbb33b57a67ed8bb7bfee43a/resize?h=46&w=221 - thaJeztah
显示剩余8条评论

5

在容器内运行bash而不会遇到换行问题的好方法在这里

docker exec -e COLUMNS="`tput cols`" -e LINES="`tput lines`" -ti container bash

1

关于sh与terminfo的评论基本上不相关。相关的部分(在给定的答案中不清楚)是命令的执行方式。tput按以下顺序检查三个特征(使用setupterm):

  1. 从terminfo数据库获取终端的大小(许多描述没有提供此信息,但使用TERM = xterm时,它为24乘以80
  2. 如果可以从操作系统获取该信息(即当前窗口大小),则获取实际行数。
  3. LINESCOLUMNS环境变量。

不使用交互式shell运行的命令可能以一种方式执行,这种方式会排除获取当前窗口大小的可能性。例如,这是ssh的一个特性(-t选项)。此外,Docker可以设置LINES和COLUMNS变量,尽管这是无意义的。

无论是情况1还是情况3足以解释这种行为;引入时间延迟和竞争并不能解释这一点。


我已经更新了我的回答:https://github.com/opencontainers/runtime-spec/pull/563 将为所有平台解决该问题。 - VonC


1

我刚刚使用版本 Docker version 18.06.1-ce, build e68fc7a 进行了测试。它似乎存在同样的问题。不过,在github issue中有一个人提供了一个实用的解决方法:

docker run --rm -it -e COLUMNS=$COLUMNS -e LINES=$LINES -e TERM=$TERM -it ubuntu:16.04 tput lines

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