我正在传递以下简单的shell脚本来打击LXC容器:
apt-get update apt-get install postgresql -y sudo -u postgres psql -c 'create database dvdrental;'
我正在使用的实际命令是:
cat sample.sh | lxc-attach -n test-container -- /bin/bash
我这样做的原因,而不是上传脚本到容器中,并执行这种方式是这只是一个更复杂的应用程序,我们正在build设的理念的certificate,必须采取命令的标准input和运行在容器里。
除了一件事,它似乎工作得很好。 当postgresql仍在安装时,它会移到psql
命令上,也就是说,
[...] 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. [...]
注意sudo -u postgres psql -c 'create database dvdrental;'
在输出中间的一行。 有趣的是,在apt-get命令的下载部分完成后,它总是显示出来。
任何人都知道可能是由什么造成的?
哦,这是一个有趣的 。
简短的回答是:它发生在那里是因为apt(或者它分叉的东西)在执行时读取了stdin,并且读取了脚本的剩余行,因为在那个时候仍然是stdin。 简短的解决方法:在apt-get install
行的末尾放置</dev/null
,然后继续前进。
长的回答(严重的是,这是一个biggun):从正在运行的进程的angular度来看,stdin / stdout / stderr没有什么特别之处。 它们只是文件描述符,文件描述符在进程之间在进程之间共享。 所以,发生什么(或多或less)是:
在你的terminal中交互运行的bash副本将打开一个新的pipe
(2),然后分叉一个新的进程,closures现有的stdout,然后使stdout文件描述符(1)成为pipe道的写入端(参见dup2
(2) ))。 那个subprocess然后exec
s cat sample.sh
,读取文件并把它写到它认为是标准输出(但实际上是pipe道的写入端)。
在terminal中交互运行的bash副本派生出另一个新进程,这次closures了现有的stdin ,然后使stdin文件描述符(0)成为之前讨论过的同一pipe道的读取器结尾(再次调用dup2
)。 这个过程然后exec
你的lxc-attach
过程。
如果一路上没有任何干扰stdin的话(在这种情况下,它不会)干扰stdin,那么每一个从pipe道读者端获得stdin的进程都会分配一个完全相同的文件描述符,同样的pipe道,其中sample.sh
的内容填入它,作为其标准。 从该文件描述符中读取的任何进程现在都将消耗读取的字节,并且没有其他从该文件描述符读取的进程将获得这些特定字节。 注意这一点; 你会再次看到这个材料。
当你意大利水柜式pipe道盛会的最后一刻的狂欢终于开始时,它会从pipe道中读取“一些”的数据(因为这是bash所做的,当没有参数的情况下调用的时候一个tty作为标准input)。 通过strace
的魔力,我刚刚证实了bash实际上一次只读取一个字符(而不是读入4k块),所以每个单独的字符都不是bash命令的一部分已经或者现在正在执行,仍然会坐在pipe道中,这个pipe道是什么样子的。
当bash执行脚本中的第二个命令时, apt-get install
tra la la,它会分叉一个新进程。 它inheritance了bash的所有文件描述符, 包括 (最重要的) 我们的好朋友pipe-which-is-stdin 。 这也适用于任何apt-get
叉的进程(也就是说,让我向你保证,相当多)。 其中之一,或者apt-get
本身,正在决定读取stdin并将其写入标准输出(或者可能是stderr)。
apt-get install
完成后,bash会再次从stdin中读取下一个要执行的内容。 因为别的东西已经把所有东西都读出来了,但是什么都没有了,而且bash会“好吧,我猜我已经完成了”然后退出。 再次,pipe道是空的,因为其他东西已经读取干,共享一个文件描述符的所有东西都分享它的赏金。
毫无疑问,解决“共享标准input”问题的办法就是不要在一个兄弟会派对上通过标准input。 由于不能阻止fork
(2)自动给每个人使用相同的文件描述符,所以你需要告诉bash,而不是让apt-get
(以及其他任何从这个可爱的,甜美的pipe道上非法apt-get
东西)反过来。 最简单的事情就是/dev/null
– 你所有的“Dave的不在这里,男人”永远忠实,从来没有完整的源泉。 这是“inputredirect”的领域,这就是</dev/null
所做的 – 它说:“哎呀,在你exec
apt-get
,把stdin(文件描述符0)换成你的文件描述符从打开/dev/null
“。
读者完成一个练习:尝试在apt-get install
命令后面加上</dev/zero
,并解释为什么会发生什么情况。