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