Sov*_*ero 3 bash lxc ubuntu-14.04
我将以下简单的 shell 脚本传递给 LXC 容器上的 bash:
apt-get update
apt-get install postgresql -y
sudo -u postgres psql -c 'create database dvdrental;'
Run Code Online (Sandbox Code Playgroud)
我用来运行它的实际命令是:
cat sample.sh | lxc-attach -n test-container -- /bin/bash
Run Code Online (Sandbox Code Playgroud)
我这样做而不是将脚本上传到容器并以这种方式执行的原因是,这只是我们正在构建的更复杂的应用程序的概念证明,该应用程序必须通过标准输入接收命令并运行它们在容器中。
它似乎工作得很好,除了一件事。它psql
在 postgresql 仍在安装时移动到命令上,即,
[...]
Get:21 http://archive.ubuntu.com/ubuntu/ trusty/main ssl-cert all 1.0.33 [16.6 kB]
Get:22 http://archive.ubuntu.com/ubuntu/ trusty-updates/main postgresql-common all 154ubuntu1 [103 kB]
Get:23 http://archive.ubuntu.com/ubuntu/ trusty-updates/main postgresql-9.3 amd64 9.3.10-0ubuntu0.14.04 [2,669 kB]
Get:24 http://archive.ubuntu.com/ubuntu/ trusty-updates/main postgresql all 9.3+154ubuntu1 [5,038 B]
Fetched 5,834 kB in 28s (207 kB/s)
Preconfiguring packages ...
sudo -u postgres psql -c 'create database dvdrental;'
Selecting previously unselected package libroken18-heimdal:amd64.
(Reading database ... 14599 files and directories currently installed.)
Preparing to unpack .../libroken18-heimdal_1.6~git20131207+dfsg-1ubuntu1.1_amd64.deb ...
Unpacking libroken18-heimdal:amd64 (1.6~git20131207+dfsg-1ubuntu1.1) ...
Selecting previously unselected package libasn1-8-heimdal:amd64.
[...]
Run Code Online (Sandbox Code Playgroud)
请注意sudo -u postgres psql -c 'create database dvdrental;'
输出中间是否存在该行。有趣的是,它总是在 apt-get 命令的下载部分完成后立即显示......
有谁知道这可能是什么原因造成的?
哦哦,这是一个有趣的。
简短的回答:它发生在那里是因为 apt (或它分叉的东西)在执行过程中正在读取标准输入,并且它读取脚本的其余行,因为那是当时仍在标准输入中的内容。短期的解决办法:把</dev/null
在年底apt-get install
线,并与你的一天前进。
长答案(说真的,这是一个大炮):从正在运行的进程的角度来看,stdin/stdout/stderr 没有什么特别之处。它们只是文件描述符,并且文件描述符在进程之间被分叉时共享。所以,会发生什么(或多或少)是:
在终端中以交互方式运行的 bash 副本打开一个新的pipe
(2),然后派生一个新进程,关闭现有的 stdout,然后使 stdout 文件描述符 (1) 成为管道的写入端(请参阅dup2
(2)) . 该子进程然后exec
s cat sample.sh
,它读取文件并将其写入它认为是标准输出的内容(但实际上是管道的写入端)。
在终端中交互运行的 bash 副本派生出另一个新进程,这次关闭现有的stdin,然后使 stdin 文件描述符 (0) 成为前面讨论过的同一管道的读取器端(再次调用dup2
)。这个过程就是exec
你的lxc-attach
过程。
如果一路上标准输入(这不,在这种特殊情况下),那么什么会干扰每一个被从其中一个得到了管作为标准输入的读者到底分叉过程也有完全相同的文件描述符,附加到相同的管道,其中sample.sh
填充了内容作为其标准输入。 从该文件描述符读取的任何进程现在都将消耗读取的字节,并且从该文件描述符读取的任何其他进程都不会获得这些特定字节。请注意这一点;你会再次看到这个材料。
当位于意大利抽水马桶式管道盛会远端的 bash 终于开始时,它会从管道中读取“一些”数据,即它的标准输入(因为这就是 bash 所做的,在不带参数和不带参数的情况下调用时)作为标准输入的 tty)。通过 的魔法strace
,我刚刚确认 bash 实际上一次读取一个字符(而不是读取它,比如说,4k 块),所以每个不属于 bash 的命令的一部分的单个字符或当前正在执行,仍将位于 pipe-which-bash-has-as-its-stdin 中。
当 bash 执行脚本中的第二个命令apt-get install
tra la la 时,它会派生一个新进程。它继承了 bash 的所有文件描述符,包括(最重要的)我们的好朋友 pipe-which-is-stdin。这也适用于任何apt-get
分叉的进程(这是,让我向你保证,相当多)。其中之一,或者apt-get
它本身,决定读取标准输入并将它读取的任何内容写入标准输出(或可能是标准错误)。
当apt-get install
完成时,bash发现了什么,接下来的事情执行是从标准再次阅读。因为其他东西已经从管道中读取了所有内容,但是什么都没有了,bash 表示“哦,好吧,我想我已经完成了”然后退出。同样,管道是空的,因为其他东西已经把它读干了,所有共享一个文件描述符的东西都共享它的赏金。
毫不奇怪,“共享标准输入”问题的解决方案是停止像兄弟会派对上的烟枪一样传递标准输入。由于您无法阻止fork
(2) 自动为每个人提供相同的文件描述符,因此您需要告诉 bash 提供apt-get
(以及其他任何从那个甜蜜的管道中非法啜饮的东西)其他东西,而不是. 最容易给予的东西是/dev/null
——永远忠实、永不满足的所有“戴夫不在这里,伙计”喜悦的来源。这就是“输入重定向”,域这是什么</dev/null
呢-它说,“哎庆典,之前你exec
说apt-get
,换出标准输入(文件描述符0)与您打开得到的文件描述符/dev/null
”。
给读者的一个练习,作为结束:尝试</dev/zero
在apt-get install
命令之后放置,并解释为什么会发生这种情况。