Поиск по сайту:

Как (и почему) предотвратить слишком ранний перезапуск сценария Bash


Быстрые ссылки

  • Сделайте передышку
  • Наша стратегия, основанная на времени
  • Время в Linux
  • Хранение и извлечение времени
  • Собираем все вместе
  • Полезно в других сценариях

Иногда полезно убедиться, что сценарий оболочки Bash не запускается слишком часто. Вот изящный способ установить ограничение по времени, которое должно истечь, прежде чем скрипт сможет снова запуститься.

Сделайте передышку

В зависимости от того, что он делает и какие другие процессы он может запустить, сценарий Bash может потреблять столько же ОЗУ и процессорного времени, сколько и любой другой ресурсоемкий процесс. Может быть полезно ограничить частоту запуска такого сценария.

Установление периода передышки между каждым запуском тяжеловесного сценария не позволяет ему перегружать ресурсы. Сценарии, требующие больших вычислительных затрат, могут монополизировать компьютеры до такой степени, что производительность других пользователей упадет. В крайних случаях, если сценарий вызывает, например, большую смену дисков, он может даже ускорить выход из строя вашего оборудования.

Конечно, разработка схемы, ограничивающей время перезапуска сценария, добавляет код в ваш сценарий и дает ему еще одну задачу. Это может показаться контрпродуктивным, но если проверки легкие и быстрые, их крошечные накладные расходы намного перевешиваются экономией ресурсов.

Наша стратегия, основанная на времени

Мы хотим установить минимальный период времени, который должен истечь, прежде чем сценарий можно будет повторить. Мы назовем этот период промежуточным. Мы определим его как время между завершением предыдущего запуска и началом нового запуска.

Нам нужно сохранить время завершения сценария, чтобы при следующем запуске сценария мы могли его получить. Поскольку сценарий может легко определить текущее время, он может определить разницу между временем своего запуска и временем завершения предыдущего сценария.

Если эта разница меньше нашего приемлемого промежуточного значения, сценарий завершится.

Время в Linux

Linux ведет отсчет секунд со времени (второй) эпохи Linux, которая произошла в полночь 1 января 1970 года по всемирному координированному времени. Мы можем увидеть время и дату, введя команду date.

date

Мы можем передать спецификаторы формата на сегодняшний день, чтобы получить результат в различных рендерингах. Чтобы увидеть время в секундах с начала эпохи, используйте строчную букву «s»:

date +%s

Возможность доступа ко времени как к одному целому числу упрощает сравнение двух значений времени и определение интервала времени между ними. Это идеально подходит для наших нужд, и мы этим воспользуемся.

Хранение и извлечение времени

Мы можем легко записать время в файл, просто перенаправив вывод команды date. Мы можем использовать cat, чтобы убедиться, что это сработало.

date +%s > temp.dat
cat temp.dat

Это дает нам возможность хранить нашу временную метку. Обратите внимание, что мы используем один > для перенаправления, поэтому файл каждый раз создается заново и содержит только одну запись.

Внутри нашего скрипта нам нужно открыть файл временной метки и прочитать сохраненное значение. Это значение должно храниться в переменной, чтобы наш скрипт мог его использовать.

Это звучит довольно сложно, но есть простой трюк, который мы можем использовать. Внутри нашего скрипта мы будем использовать команду source для чтения файла метки времени. Команды внутри исходного файла выполняются так, как если бы они были командами внутри нашего скрипта.

Когда мы сохраняем метку времени, мы фактически сохраняем команду, которая создает переменную и присваивает ей значение времени. Когда файл будет получен, наш скрипт выполнит эту команду, создаст переменную и сохранит в ней значение времени, так что все это будет сделано за нас.

Мы можем проверить этот процесс в командной строке. Составляем и записываем команду в файл. Команда создает переменную с именем previous_exit, для которой установлено количество секунд, прошедших с начала эпохи. Мы исходим из файла. Затем мы проверяем, что переменная с именем previous_exit теперь существует, и смотрим, какое значение она содержит.

echo "previous_exit=$(date +%s)" > timestamp.log
source timestamp.log
echo $previous_exit

Если мы проверим содержимое файла, мы сможем убедиться, что значение, хранимое в переменной, совпадает с тем, которое находится в файле.

cat timestamp.log 

Это хорошее и простое решение для хранения и извлечения нашей временной стоимости.

Собираем все вместе

Давайте пройдемся по различным элементам сценария.

Мой скрипт будет хранить временные метки в файле с именем .timestamp.log. Обратите внимание, что первый символ — это точка «.», что означает, что это скрытый файл. Он будет храниться в моем домашнем каталоге.

Скрипт создает переменную timestamp_log для хранения пути и имени файла.

Далее определяется функция set_timestamp. При вызове эта функция записывает переданное ей значение в файл timestamp.log.

#!/bin/bash
# location of the timestamp log file
timestamp_log="/home/dave/.timestamp.log"
set_timestamp() {
  echo "previous_exit=$1" > $timestamp_log
}

Поскольку файл timestamp.log обновляется (и создается, если он не существует) при завершении работы сценария, при первом запуске сценария файл timestamp.log не будет существовать. Это может вызвать проблему, когда сценарий попытается прочитать его.

Чтобы решить эту проблему и защититься от ситуаций, когда файл timestamp.log мог быть удален, мы проверяем его существование. Если он не существует, мы создаем его, сохраняя в нем фиктивное значение времени, равное нулю.

# If the timestamp log file doesn't exist, create it
if [ ! -f $timestamp_log ]; then
  set_timestamp 0
fi

Теперь мы можем безопасно получить файл и прочитать инструкцию внутри него. Это устанавливает для переменной previous_exit предыдущую временную метку.

# get the last exit time as variable called previous_exit
source $timestamp_log

Теперь, когда у нас есть значение временной метки, мы можем вычислить промежуточный период между предыдущей временной меткой и текущим временем.

# get the interim period since the last exit
interim=$(( $(date +%s)-$previous_exit ))

Теперь мы можем выполнить простой тест, чтобы увидеть, прошло ли достаточно времени для запуска сценария. Для тестирования я использую произвольное и короткое значение в пять секунд.

if (( $interim <= 5 )); then 
  echo "Too soon... $interim seconds..."
  exit 1; 
fi
# your actual script starts here 
echo "Running..."

Если промежуточный период превышает пять секунд, сценарий может продолжить работу. По завершении мы записываем текущее время в файл timestamp.log, вызывая функцию set_timestamp.

# set the new timestamp
set_timestamp $(date +%s)
exit 0

Вот весь сценарий.

#!/bin/bash
# location of the timestamp log file
timestamp_log="/home/dave/.timestamp.log"
set_timestamp() {
  echo "previous_exit=$1" > $timestamp_log
}
# If the timestamp log doesn't exist, create it
if [ ! -f $timestamp_log ]; then
  set_timestamp 0
fi
# get the last exit time as a variable called previous_exit
source $timestamp_log
# get the interim period since the last exit
interim=$(( $(date +%s)-$previous_exit ))
if (( $interim <= 5 )); then 
    echo "Too soon... $interim seconds..."
  exit 1; 
fi
# set the new timestamp
set_timestamp $(date +%s)
echo "Running..."
exit 0

Скопируйте это в свой любимый редактор и сохраните как файл с именем tc.sh. Не забудьте изменить значение timestamp_log= в строке 4, чтобы оно указывало на место на вашем компьютере, где должен храниться файл timestamp.log.

Сделайте свой скрипт исполняемым.

chmod +x tc.sh

И теперь мы можем его запустить.

./tc.sh

Последующие попытки выполнения в течение пятисекундного периода исключения автоматически прекращаются. По истечении пяти секунд мы можем запустить скрипт еще раз.

Полезно в других сценариях

Помните: если ваш сценарий имеет ветвящееся выполнение и может завершиться в разных точках сценария, вам нужно будет вызывать set_timestamp перед каждым возможным выходом. Именно поэтому стоило создать функцию set_timestamp, хотя в этом скрипте она используется всего два раза.

Трюк с сохранением имени переменной и ее значения в исходном файле можно использовать для чтения в файле конфигурации. Вам просто нужно будет записать список имен переменных и их значений в файл и получить его из вашего скрипта.

Статьи по данной тематике: