Сценарий оболочки 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 ...

Кто-нибудь знает, что может быть причиной этого?

3
задан 30 November 2015 в 05:28
1 ответ

Ооооо, это забавный .

Краткий ответ: это происходит там, потому что apt (или что-то, что он разветвляет) читает stdin в этот момент своего выполнения, и он читает оставшиеся строки скрипта, потому что это то, что все еще находится в stdin в этот момент. Краткое исправление: поместите в конец строки apt-get install и продолжайте свой день.

Длинный ответ (серьезно, это это большой пулемет): в stdin / stdout / stderr нет ничего особенного с точки зрения запущенного процесса. Это просто файловые дескрипторы, а файловые дескрипторы совместно используются процессами при их разветвлении. Итак, что происходит (более или менее):

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

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

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

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

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

  5. Когда apt-get install завершится , bash выясняет, что нужно выполнить следующим образом, путем повторного чтения из стандартного ввода-вывода. Потому что что-то else уже прочитало все из конвейера, хотя ничего не осталось, и bash выдает «ну ладно, тогда я думаю, я закончил» и завершает работу. Опять же, канал пуст, потому что что-то еще уже прочитало его всухую, и все, что разделяет один файловый дескриптор, получает от него награду.

Решение проблемы "общего стандартного ввода", что неудивительно, состоит в том, чтобы перестать передавать стандартный ввод, как бонг на братской вечеринке. Поскольку вы не можете запретить fork (2) автоматически предоставлять всем одинаковые файловые дескрипторы, вам нужно вместо этого сообщить bash, чтобы он дал apt-get (и все остальное, что требует незаконный глоток из этой сладкой сладкой трубки) что-нибудь еще , чтобы вместо этого выпить. Проще всего дать / dev / null - этот вечно верный, никогда не полный источник всех ваших восхищений «Дэйва здесь нет». Это область «перенаправления ввода», что и делает - он говорит: «Эй, bash, перед вами exec , что apt- get , замените stdin (файловый дескриптор 0) на файловый дескриптор, который вы получите при открытии / dev / null ".

Упражнение для читателя, завершение: попробуйте поставить после команды apt-get install и объясните, почему происходит то, что происходит.

5
ответ дан 3 December 2019 в 05:41

Теги

Похожие вопросы