Я передаю следующий простой сценарий оболочки для bash в контейнере 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
Причина, по которой я делаю это таким образом, а не загружаю скрипт в контейнер и выполняю его таким образом, заключается в том, что это просто доказательство концепции для гораздо более сложного приложения, которое мы создаем, которое должно принимать команды через стандартный ввод и запускать их. в контейнере.
Вроде отлично работает, за исключением одного. Он переходит на 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.
[...]
Обратите внимание на существование sudo -u postgres psql -c 'create database dvdrental;'
линия в середине вывода. Интересно, что он всегда появляется сразу после завершения загрузки команды apt-get ...
Кто-нибудь знает, что может быть причиной этого?
Ооооо, это весело один.
Короткий ответ: это происходит там, потому что apt (или что-то, что он разветвляет) читает stdin в этот момент своего выполнения, и он читает оставшиеся строки скрипта, потому что это то, что все еще находится в stdin в этот момент. Краткое исправление: положить </dev/null
в конце apt-get install
линия и продолжайте свой день.
Длинный ответ (серьезно, это большая удача): в stdin / stdout / stderr нет ничего особенного с точки зрения запущенного процесса. Это просто файловые дескрипторы, а файловые дескрипторы совместно используются процессами при их разветвлении. Итак, что происходит (более или менее):
Копия bash, запущенная в интерактивном режиме в вашем терминале, открывает новый pipe
(2), затем формирует новый процесс, который закрывает существующий стандартный вывод, а затем делает дескриптор файла стандартного вывода (1) записывающим концом канала (см. dup2
(2)). Тогда этот дочерний процесс exec
s cat sample.sh
, который читает файл и записывает его в то, что он думает - это стандартный вывод (но на самом деле это конец канала записи).
Копия bash, запущенная в интерактивном режиме в вашем терминале, порождает еще один новый процесс, на этот раз закрывая существующий стандартный ввод, а затем делает дескриптор файла stdin (0) считывающим концом того же канала, о котором говорилось ранее (опять же, с вызовом dup2
). Затем этот процесс exec
твой lxc-attach
обработать.
Если на пути к stdin ничего не мешает (чего не происходит в данном конкретном случае), то каждый процесс, который разветвляется от того, который получил читательский конец канала, поскольку stdin будет также иметь тот же дескриптор файла, прикрепленный к тому же каналу, который содержал sample.sh
вставлен в него, как его stdin. любой процесс, который читает из этого файлового дескриптора, теперь будет использовать прочитанные байты, и ни один другой процесс, который читает из этого файлового дескриптора, не получит эти конкретные байты. Обратите на это внимание; вы снова увидите этот материал.
Когда bash в дальнем конце вашей итальянской сантехнической феерии в стиле унитаза наконец-то начнется, он прочитает "некоторые" данные из канала, который является его стандартным вводом (потому что это то, что делает bash, когда вызывается без аргументов и без tty как стандартный ввод). Через магию strace
, Я только что подтвердил, что bash действительно считывает свой ввод по одному символу за раз (вместо того, чтобы читать его, скажем, блоками по 4 КБ), поэтому каждый отдельный символ, который не является частью команды, которую имеет bash или которая в настоящее время , выполняющийся, по-прежнему будет находиться в pipe-which-bash-has-as-its-stdin.
Когда bash выполняет вторую команду вашего скрипта, apt-get install
тра-ла-ла, это вызывает новый процесс. Которая наследует все файловые дескрипторы bash, включая (самое главное) наш хороший друг трубка, которая есть-стандарт. То же самое происходит и с любыми процессами, которые apt-get
вилок (а их, уверяю вас, довольно много). Один из них, или apt-get
сам решает читать stdin и записывать все, что он читает, в stdout (или, возможно, stderr).
Когда apt-get install
завершается, bash выясняет, что нужно выполнить, еще раз считывая из стандартного ввода. Потому что что-то еще уже прочитал все из трубы, правда, ничего не осталось, и bash цифры «да ладно, я думаю, что я сделал тогда» и выходит. Опять же, канал пуст, потому что что-то другое уже прочитало его всухую, и все, что использует один файловый дескриптор, получает от него награду.
Неудивительно, что решение проблемы "общего stdin" состоит в том, чтобы перестать передавать stdin, как бонг на братской вечеринке. Поскольку ты не можешь остановиться fork
(2) от автоматического предоставления всем одинаковых файловых дескрипторов вам нужно вместо этого указать bash, чтобы apt-get
(и все остальное, что делает незаконный глоток из этой сладкой трубки) еще вместо этого, чтобы продолжить. Проще всего дать /dev/null
- этот вечно верный, никогда не полный источник всех ваших восхищений "Дэйва здесь нет, мужик". Это область "перенаправления ввода", что и является </dev/null
делает - он говорит: "Эй, баш, перед тобой exec
который apt-get
, замените stdin (дескриптор файла 0) на дескриптор файла, который вы получите при открытии /dev/null
".
Завершение упражнения для читателя: попробуйте поставить </dev/zero
после apt-get install
команду и объясните, почему происходит то, что происходит.