Назад | Перейти на главную страницу

Сценарий оболочки Bash выполняется не по порядку при передаче через стандартный ввод в контейнер LXC

Я передаю следующий простой сценарий оболочки для 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 нет ничего особенного с точки зрения запущенного процесса. Это просто файловые дескрипторы, а файловые дескрипторы совместно используются процессами при их разветвлении. Итак, что происходит (более или менее):

  1. Копия bash, запущенная в интерактивном режиме в вашем терминале, открывает новый pipe(2), затем формирует новый процесс, который закрывает существующий стандартный вывод, а затем делает дескриптор файла стандартного вывода (1) записывающим концом канала (см. dup2(2)). Тогда этот дочерний процесс execs cat sample.sh, который читает файл и записывает его в то, что он думает - это стандартный вывод (но на самом деле это конец канала записи).

  2. Копия bash, запущенная в интерактивном режиме в вашем терминале, порождает еще один новый процесс, на этот раз закрывая существующий стандартный ввод, а затем делает дескриптор файла stdin (0) считывающим концом того же канала, о котором говорилось ранее (опять же, с вызовом dup2). Затем этот процесс execтвой lxc-attach обработать.

    Если на пути к stdin ничего не мешает (чего не происходит в данном конкретном случае), то каждый процесс, который разветвляется от того, который получил читательский конец канала, поскольку stdin будет также иметь тот же дескриптор файла, прикрепленный к тому же каналу, который содержал sample.sh вставлен в него, как его stdin. любой процесс, который читает из этого файлового дескриптора, теперь будет использовать прочитанные байты, и ни один другой процесс, который читает из этого файлового дескриптора, не получит эти конкретные байты. Обратите на это внимание; вы снова увидите этот материал.

  3. Когда bash в дальнем конце вашей итальянской сантехнической феерии в стиле унитаза наконец-то начнется, он прочитает "некоторые" данные из канала, который является его стандартным вводом (потому что это то, что делает bash, когда вызывается без аргументов и без tty как стандартный ввод). Через магию strace, Я только что подтвердил, что bash действительно считывает свой ввод по одному символу за раз (вместо того, чтобы читать его, скажем, блоками по 4 КБ), поэтому каждый отдельный символ, который не является частью команды, которую имеет bash или которая в настоящее время , выполняющийся, по-прежнему будет находиться в pipe-which-bash-has-as-its-stdin.

  4. Когда bash выполняет вторую команду вашего скрипта, apt-get install тра-ла-ла, это вызывает новый процесс. Которая наследует все файловые дескрипторы bash, включая (самое главное) наш хороший друг трубка, которая есть-стандарт. То же самое происходит и с любыми процессами, которые apt-get вилок (а их, уверяю вас, довольно много). Один из них, или apt-get сам решает читать stdin и записывать все, что он читает, в stdout (или, возможно, stderr).

  5. Когда 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 команду и объясните, почему происходит то, что происходит.