Доброго всем настроения!
Прочитал я вот эту статью, и решил немного сам взять в руки шашки, и попробовать сделать что-нибудь приятное для себя и для других.
Мой скрипт не делает никаких полезных вещей, но думаю для более менее начинающих писателей на bash он чему-нибудь научит, да и если будут комментарии, то и я научусь от тех людей которые укажут на мои ошибки.
Скрипт будет запускаться в фоне демоном. Сразу думаю надо договориться что сам процесс который будет висеть в памяти постоянно я буду называть «Родителем». Родитель будет в определенном каталоге искать определенный файл, и если он существует, то файл будет удален и запущен процесс, который я буду называть «Потомок», целью которого будет просто спать какое-то время, и после чего завершиться. Но Родитель не должен будет запускать более одного Потомка в единицу времени. В принципе, если Вы прочитали вышеуказанную статью, то смысл я думаю понятен.
Итак, определим наши переменные
Далее для удобного управления запуском и остановом Родителя напишем небольшое условие
Каркас готов, все переменные тоже определены, теперь надо бы описать используемые функции
Теперь опишу функции по мере их усложнения. Самая простая их них это функция usage, выглядит она так
Далее по слжности идет функция _log
Тепеь функция остановки демона
Здесь я не использую функцию логирования, т.к. здесь сообщения должны выводиться на консоль.
Теперь пожалуй приведу самую сложную функцию start. В ней находится вся логика работы и сам момент демонизации скрипта.
Ну и сама функция запуска Потомка
Ну и теперь если все вышеприведенное объеденить в файл и дать ему права на выполнение, думаю у Вас это не составит труда. Кстати перед запуском советую изменить значение переменной WATCH_DIR на путь к каталогу, в котором скрипт будет искать файл с именем run_Lola_run.
Вот мой вывод лог файла
Надеюсь кому-нибудь данный пост поможет в освоении bash.
ЗЫ: Внимательный читатель наверняка увидел что в логе везде один и тот-же номер процесса. Т.к. в bash нет чистого форк, здесь происходит его, можно сказать, эмулирование через запуск необходимого кода в ()&, что запускает его в отдельном процессе, но при этом сохраняет переменные неизменными. А мы знаем что номер текущего процесса в bash хранится в переменной $$. Поэтому в лог файле и отображается один и тот же номер процесса. Именно поэтому функция start заканчивается строкой echo $! > ${PID_FILE}.
Прочитал я вот эту статью, и решил немного сам взять в руки шашки, и попробовать сделать что-нибудь приятное для себя и для других.
Мой скрипт не делает никаких полезных вещей, но думаю для более менее начинающих писателей на 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}.
