Доброго всем настроения!
Прочитал я вот эту статью, и решил немного сам взять в руки шашки, и попробовать сделать что-нибудь приятное для себя и для других.
Мой скрипт не делает никаких полезных вещей, но думаю для более менее начинающих писателей на 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}.