Прочитал я вот эту статью, и решил немного сам взять в руки шашки, и попробовать сделать что-нибудь приятное для себя и для других.
Мой скрипт не делает никаких полезных вещей, но думаю для более менее начинающих писателей на bash он чему-нибудь научит, да и если будут комментарии, то и я научусь от тех людей которые укажут на мои ошибки.
Вводная
Скрипт будет запускаться в фоне демоном. Сразу думаю надо договориться что сам процесс который будет висеть в памяти постоянно я буду называть «Родителем». Родитель будет в определенном каталоге искать определенный файл, и если он существует, то файл будет удален и запущен процесс, который я буду называть «Потомок», целью которого будет просто спать какое-то время, и после чего завершиться. Но Родитель не должен будет запускать более одного Потомка в единицу времени. В принципе, если Вы прочитали вышеуказанную статью, то смысл я думаю понятен.
Начнем-с
Итак, определим наши переменные
# Имя файла который будем искать в каталоге FILE_NAME="run_Lola_run" # Каталог в котором будем искать файл WATCH_DIR="/home/mcleod/test" # Имя файла по которому будем определять запущен ли уже Родитель LOCK_FILE="${WATCH_DIR}/monitor_file.lock" # Файл где будем хранить номер работающего процесса Родителя PID_FILE="${WATCH_DIR}/monitor_file.pid" # Имя файла по которому будем определять запущен ли Потомок JOB_LOCK_FILE="${WATCH_DIR}/job_monitor_file.lock" # В этот файл будем писать ход выполнения скрипта LOG="${WATCH_DIR}/monitor_file_work.log" # В этот файл будут попадать ошибки при работе скрипта ERR_LOG="${WATCH_DIR}/monitor_file_error.log" # Определяем максимельное время работы Потомка в секундах RANGE=100
Далее для удобного управления запуском и остановом Родителя напишем небольшое условие
case $1 in "start") start ;; "stop") stop ;; *) usage ;; esac exit
Каркас готов, все переменные тоже определены, теперь надо бы описать используемые функции
- start — функция запуска, которая переводит скрипт в режим демона
- stop — функция остановки демона
- usage — функция вывода на экран помощи
- _log — функция записи в лог файл
- run_job — функция запуска Потомка
Теперь опишу функции по мере их усложнения. Самая простая их них это функция usage, выглядит она так
# Выводим помощь usage() { echo "$0 (start|stop)" }
Далее по слжности идет функция _log
# Функция логирования _log() { process=$1 shift echo "${process}[$$]: $*" }
Тепеь функция остановки демона
# Функция остановки демона stop() { # Если существует pid файл, то убиваем процесс с номером из pid файла if [ -e ${PID_FILE} ] then _pid=$(cat ${PID_FILE}) kill $_pid rt=$? if [ "$rt" == "0" ] then echo "Daemon stop" else echo "Error stop daemon" fi else echo "Daemon is't running" fi }
Здесь я не использую функцию логирования, т.к. здесь сообщения должны выводиться на консоль.
Теперь пожалуй приведу самую сложную функцию start. В ней находится вся логика работы и сам момент демонизации скрипта.
# Функция запуска демона start() { # Если существует файл с pid процесса не запускаем еще одну копию демона if [ -e $PID_FILE ] then _pid=$(cat ${PID_FILE}) if [ -e /proc/${_pid} ] then echo "Daemon already running with pid = $_pid" exit 0 fi fi # Создаем файлы логов touch ${LOG} touch ${ERR_LOG} # переходим в корень, что бы не блокировать фс cd / # Перенаправляем стандартный вывод, вывод ошибок и стандартный ввод exec > $LOG exec 2> $ERR_LOG exec < /dev/null # Запускаем подготовленную копию процесса, вообщем форкаемся. Здесь происходит вся работа скрипта ( # Не забываем удалять файл с номером процесса и файл очереди при выходе trap "{ rm -f ${PID_FILE}; exit 255; }" TERM INT EXIT # Основной цикл работы скрипта while [ 1 ] do # Просматриваем каталог на наличие файла if ls -1 ${WATCH_DIR} | grep "^${FILE_NAME}$" 2>&1 >/dev/null then _log "parent" "File found" rm -f ${WATCH_DIR}/${FILE_NAME} _log "parent" "File deleted" # Вычисляем сколько будет спать Потомок в секундах и запоминаем в массиве number=$RANDOM let "number %= $RANGE" _log "parent" "Genereated number $number" JOBS[${#JOBS[@]}]=$number fi # Если размер массива больше 0, то запускаем Потомка, и удаляем первый элемент из массива if [ "${#JOBS[@]}" -gt "0" ] then if [ ! -e ${JOB_LOCK_FILE} ] then run_job _log "parent" "Running job with pid $!" unset JOBS[0] JOBS=("${JOBS[@]}") _log "parent" "Jobs in queue [${#JOBS[@]}]" fi fi # Дадим процессору отдохнуть sleep 1 done exit 0 )& # Пишем pid потомка в файл, и заканчиваем работу echo $! > ${PID_FILE} }
Ну и сама функция запуска Потомка
# Функция запуска Потомка. Потомок берет первый элемент из массива JOBS, и работает run_job() { # Здесь происходит порождение Потомка. Здесь уже ничего не подготавливаем и не переопределяем стандартный ввод/вывод, т.к. это уже сделано в Родителе ( # Не забываем удалять после окончания работы файл trap "{ rm -f ${JOB_LOCK_FILE}; exit 255; }" TERM INT EXIT # Дополнительная проверка что бы убедиться что Потомок один if [ ! -e ${JOB_LOCK_FILE} ] then # Пишем номер pid процесса в файл, на всякий случай echo "$$" > ${JOB_LOCK_FILE} _log "child" "Job with pid $$" # Запоминаем первый элемент массива seconds=${JOBS[0]} # Очищаем массив, хоть память сейчас и дешевая, но зачем ее занимать зря unset JOBS _log "child" "Sleep seconds $seconds" sleep ${seconds} else _log "child" "Lock file is exists" fi # Выходим exit 0 )& }
Ну и теперь если все вышеприведенное объеденить в файл и дать ему права на выполнение, думаю у Вас это не составит труда. Кстати перед запуском советую изменить значение переменной WATCH_DIR на путь к каталогу, в котором скрипт будет искать файл с именем run_Lola_run.
Вот мой вывод лог файла
parent[32338]: File found parent[32338]: File deleted parent[32338]: Genereated number 96 parent[32338]: Running job with pid 32385 parent[32338]: Jobs in queue [0] child[32338]: Job with pid 32338 child[32338]: Sleep seconds 96 parent[32338]: File found parent[32338]: File deleted parent[32338]: Genereated number 46 parent[32338]: File found parent[32338]: File deleted parent[32338]: Genereated number 1 parent[32338]: Running job with pid 32694 parent[32338]: Jobs in queue [1] child[32338]: Job with pid 32338 child[32338]: Sleep seconds 46 parent[32338]: Running job with pid 371 parent[32338]: Jobs in queue [0] child[32338]: Job with pid 32338 child[32338]: Sleep seconds 1
Надеюсь кому-нибудь данный пост поможет в освоении bash.
ЗЫ: Внимательный читатель наверняка увидел что в логе везде один и тот-же номер процесса. Т.к. в bash нет чистого форк, здесь происходит его, можно сказать, эмулирование через запуск необходимого кода в ()&, что запускает его в отдельном процессе, но при этом сохраняет переменные неизменными. А мы знаем что номер текущего процесса в bash хранится в переменной $$. Поэтому в лог файле и отображается один и тот же номер процесса. Именно поэтому функция start заканчивается строкой echo $! > ${PID_FILE}.

