Доброго времени суток, хабражители и хабрачитатели!
Возникла у меня недавно следующая задача: требуется мониторить определенный каталог на наличие файлов, и, в случае появления в нем файла необходимо этот файл перенести вболее безопасное другое место, и запустить на нем довольно длительную обработку. Казалось бы, все просто, однако ситуация омрачается тем, что нельзя делать обработку одновременно нескольких файлов (обработка тянет файлы с буржуйских серверов, которые не позволяют качать много всего с одного IP).
На ум сразу же пришла очередь заданий (FIFO), которую хотелось бы сделать на bash (чего уж далеко ходить). Желающих получить готовое решение — прошу под хабракат.
Статья расчитана на начинающих, которые впервые слышат буквосочетание FIFO применительно к bash.
Вкратце о том, что будет сделано: мы создадим очередь команд, которые надо будет выполнять одну за одной. Скрипт, следящий за очередью, будет проверять файл-локер jobq.lock. Если его нет, значит никто никаких заданий не выполняет и можно смело брать следующую. Если же он есть, то читать что-либо из очереди не нужно и можно смело выходитьс чувством выполненного долга.
Для начала создадим очередь и место расположения наших скриптов:
В bin будут лежать наши скрипты, которые будут запускаться, а в var — все, что касается очереди (собственно, сама очередь jobq, а также файл-локер jobq.lock).
Также должны существовать рабочая, входная и выходная папки. В моем случае это ~/jobs/Input, ~/jobs/Work и ~/jobs/Output
Далее начинаем писать наши скрипты. Их получилось 3:
Начнем в порядке нумерации ($HOME/jobs/bin/mover.sh)
В этом скрипте все крайне просто. И хорошо (я надеюсь!) описаны действия в комментариях.
Осталось совсем немного — написать задание crontab'у. Будем выполнять этот скрипт каждую минуту
Переходим ко второму скрипту, который будет ставить наши задания в очередь ($HOME/jobs/bin/submit.sh):
Действительно, если не поставить & в конце задания, скрипт повиснет и будет ждать конца работы всех предыдущих заданий. Зачем это терпеть? Отправим в фон.
И, наконец, сам виновник торжества, скрипт, который читает очередь и запускает задания из нее ($HOME/jobs/bin/execute.sh):
И снова создаем новое задание нашему другу crontab'у:
Такая система стабильно работает уже пару недель, а я решил написать сюда — вдруг кому-нибудь понадобится.
Возникла у меня недавно следующая задача: требуется мониторить определенный каталог на наличие файлов, и, в случае появления в нем файла необходимо этот файл перенести в
На ум сразу же пришла очередь заданий (FIFO), которую хотелось бы сделать на bash (чего уж далеко ходить). Желающих получить готовое решение — прошу под хабракат.
Статья расчитана на начинающих, которые впервые слышат буквосочетание FIFO применительно к bash.
Вкратце о том, что будет сделано: мы создадим очередь команд, которые надо будет выполнять одну за одной. Скрипт, следящий за очередью, будет проверять файл-локер jobq.lock. Если его нет, значит никто никаких заданий не выполняет и можно смело брать следующую. Если же он есть, то читать что-либо из очереди не нужно и можно смело выходить
Для начала создадим очередь и место расположения наших скриптов:
umask 077 mkdir -p ~/jobs/var mkfifo ~/jobs/var/jobq mkdir -p ~/jobs/bin
В bin будут лежать наши скрипты, которые будут запускаться, а в var — все, что касается очереди (собственно, сама очередь jobq, а также файл-локер jobq.lock).
Также должны существовать рабочая, входная и выходная папки. В моем случае это ~/jobs/Input, ~/jobs/Work и ~/jobs/Output
Далее начинаем писать наши скрипты. Их получилось 3:
- Который следит за новыми данными и переносит их
- Который отправляет новые данные в очередь (этот скрипт выносится отдельно — о причинах можно прочитать в комментариях)
- Который, собственно, проверяет очередь и запускает оттуда задания
Начнем в порядке нумерации ($HOME/jobs/bin/mover.sh)
#!/bin/bash # Скрипт, который следит за новыми данными и переносит их # Соберем нужные нам файлы в кучу FILES_LIST=( $(ls $HOME/jobs/Input) ) # И по этой куче пройдемся циклом for raw_file in ${FILES_LIST[@]}; do mv $HOME/jobs/Input/$raw_file $HOME/jobs/Work/ # Определяем имя файла, без пути. По идее, этого делать даже не надо, но я оставлю это здесь filename=$(basename $raw_file) # Определяем имя файла без расширения name=${filename%.*} # В этот каталог будем складывать выходные результаты скрипта mkdir -p $HOME/jobs/Output/$name # Обращаемся к скрипту #2, который должен засунуть задание в очередь. # В качестве параметра скрипту передается целиком и полностью вся команда # А мы хотим запустить $HOME/complicated_task.sh -i $HOME/jobs/$raw_file -o $HOME/jobs/Output/$name >> $HOME/jobs/Output/$name/task.log $HOME/jobs/submit.sh "$HOME/complicated_task.sh -i $HOME/jobs/$raw_file -o $HOME/jobs/Output/$name >> $HOME/jobs/Output/$name/task.log" done
В этом скрипте все крайне просто. И хорошо (я надеюсь!) описаны действия в комментариях.
Осталось совсем немного — написать задание crontab'у. Будем выполнять этот скрипт каждую минуту
crontab -e * * * * * $HOME/jobs/bin/mover.sh
Переходим ко второму скрипту, который будет ставить наши задания в очередь ($HOME/jobs/bin/submit.sh):
#!/bin/bash # submit.sh. # Скрипт отправляет задания в очередь # С подобной очередью есть проблема, когда задание послано в очередь, # скрипт будет ждать, пока это задание не прочтут из очереди. # Поэтому приходится оптравлять задание в фоновый режим # (обращаем внимание на важный & в конце) echo $* > $HOME/jobs/var/jobq &
Действительно, если не поставить & в конце задания, скрипт повиснет и будет ждать конца работы всех предыдущих заданий. Зачем это терпеть? Отправим в фон.
И, наконец, сам виновник торжества, скрипт, который читает очередь и запускает задания из нее ($HOME/jobs/bin/execute.sh):
#!/bin/sh # execute.sh # Скрипт читает очередь и выполняет задания из нее # jobq.lock - файл, означающий, что другое задание уже выполняется # Если другое задание выполняется, то необходимо немедленно выйти test -f $HOME/jobs/var/jobq.lock && exit 0 # Заберем себе возможность выполнение, если не вышли раньше touch $HOME/jobs/var/jobq.lock || exit 2 # Читаем очередь read job < $HOME/jobs/var/jobq # Запускаем программу и заодно пишем лог задач: date >> $HOME/jobs/jobs.log echo " RUN: $job" >> $HOME/jobs/jobs.log echo "" >> $HOME/jobs/jobs.log eval $job #Запоминаем статус выхода status=$? # Когда закончили, освобождаем очередь rm -f $HOME/jobs/var/jobq.lock || exit 3 # Выходим с тем же кодом, что и у нашей задачи exit $status
И снова создаем новое задание нашему другу crontab'у:
crontab -e * * * * * $HOME/jobs/bin/execute.sh
Такая система стабильно работает уже пару недель, а я решил написать сюда — вдруг кому-нибудь понадобится.
