Назад
# Использование noproc в bash или как убить tail Tags: bash, разработка Была задача организовать "консольный интерфейс" для приложения через промежуточные файлы, так как приложение должно было работать постоянно, а писать ему в stdin нужно было периодически. Сделали вот так: ```bash setsid bash -c "tail -F $CONSOLE_DIR/.console_in | console-prog $NAME 2>&1; " \ &>>$CONSOLE_DIR/.console_out &1 | |-tail,422970 -F /opt/prog/ee1e565c-8125-4d4b-b33c/tmp/.console_in | `-console-prog,422971 ee1e565c | `-console-prog,422975 ee1e565c ... |-tail,412342 -F /opt/prog/ee1e565c-8125-4d4b-b33c/tmp/.console_in ... |-tail,43271 -F /opt/prog/ee1e565c-8125-4d4b-b33c/tmp/.console_in ... ``` Видим, что только один tail работает с console-prog, у остальных он завершил работу, но tail при этом остался. Интересно, почему bash тоже завершился и tail "осиротел", но я пока не понял почему. Возможно пайп можно использовать как disown :) ### Как убивать осиротевший tail? Есть много способов, как можно попробовать это исправить: ##### В коде, который перезапускает консоль, проверять что есть зависшие tail и убивать их. ```bash if ! ps aux | grep 'console-prog $NAME'; then ps aux | grep "tail -F $CONSOLE_DIR/.console_in" | while read -r t pid t; do kill $pid done ... ``` Причем нужные tail'ы мы сможем легко найти по имени файла. Проблема в том, что убивать tail нужно не только при перезапуске консоли, но и при всех штатных и внештатных завершениях работы контейнера $NAME. А так как $NAME может накапливаться много, желательно еще сделать отдельный мониторинг зависших tail, которые мы не смогли отловить в штатном режиме. Кажется, что все эти костыли из-за неправильного решения. ##### set -e; set -o pipefail Внутри запускаемой оболочки bash -c '...' можно выставить специальные флаги, которые не дадут гасить ошибку пайпом и заставит приложение завершить работу. ``` bash -c 'set -e; set -o pipefail; tail -F /tmp/1 | { sleep 1; exit 1; }' # должно завершиться, не сработало ``` ...такая была теория, но она не сработала. Возможно, есть другие спец.флаги, но я решил поискать другое решение. ##### exit trap По аналогии с предыдущим вариантом, в запускаемой оболочке можно повешать хук на ее завершение (а мы видели в pstree, что bash, в котором все запускается, завершается и tail остается один). Но тогда код запуска стал бы очень большим: функция завершения работы, установка trap, плюс нужно как-то получить pid'ы процессов... В одну строчку не влезло, пойдем дальше. ##### tail --pid= Сейчас стоит упомянуть, что у tail есть специальная опция --pid: ``` --pid=PID с ключом -f, прерваться, когда процесс PID завершает работу ``` То что нужно! Но непонятно как получить pid от console-prog. ##### exec и доп. дескрипторы? Если мы сначала запустим console-prog, получим его pid, а потом "подключим" в него tail с --pid - должно сработать. Наверняка это можно сделать через доп. дескрипторы и команду exec! Но как это сделать я сходу не придумал... ##### mkfifo Стандартными средствами linux можно сделать файловый именованный канал и подключить его к приложению. ``` Example # mkfifo test # cat test | cat& qwerty [1]+ Done cat test | cat # echo qwerty > test ... Зависли ... ``` С его помощью мы могли бы разрулить порядок запуска tail и console-prog. А по-хорошему, tail с файлом нам становится не нужен, ведь работа через fifo - выглядит как более правильный интерфейс для консоли! Но во-первых, менять интерфейс, без изучения нового инструмента, не хотелось. Во-вторых, во время испытания он уже показал различия: если из пайпа ни кто не будет читать, то пишущее приложение зависает. А это не сулит ничего хорошего (мало ли какой скрипт пользуется консолью). Решение хорошее, но на перспективу. ##### noproc В bash версии 4 завезли сопроцессы, как говорят, из ksh. [Статья на bash-hackers](https://wiki.bash-hackers.org/syntax/keywords/coproc). Благодаря им, мы можем запустить нужное нам приложение и уже потом подключить его к пайпу. Получаем: ```bash setsid bash -c "coproc console-prog $NAME &>>$CONSOLE_DIR/tmp/.console_out; \ tail --pid=\$COPROC_PID -F $CONSOLE_DIR/tmp/.console_in >&\${COPROC[1]}" \ &3 ) 3>pid | nc -l -p 9977 kill $(
&3 ) 3>pid | sleep 10; kill $(cat pid) # убъёт tail только когда завершится sleep ``` Если из 3го дескриптора прочитать pid в переменную, а не записывать в файл - будет еще круче. Но мне уже не хочется переделывать, merge-request с noproc уже принят :) Но вы - берите на вооружение!