Выполнять скрипт bash буквально через каждые 3 дня

8

Я хочу выполнять скрипт оболочки буквально через каждые 3 дня. Использование crontab с 01 00 */3 * *фактически не выполнит условие, потому что оно будет работать 31-го числа, а затем снова 1-го числа месяца. */3Синтаксис такой же , как говорят 1,4,7 ... 25,28,31.

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

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

if (! (date("z") % 3)) {
     exit;
}

main.sh: line 1: syntax error near unexpected token `"z"'
main.sh: line 1: `if (! (date("z") % 3)) {'
Таави
источник
4
Что ты конкретно имеешь ввиду? Как */3не работает? «если 3 дня не прошло»: три дня с чего? Пожалуйста, отредактируйте свой вопрос и уточните .
Тердон
4
буквально буквально? Это похоже на х / у вопрос, и вы можете поговорить о том, что вы пытаетесь сделать. Вы пытаетесь остановить запуск скрипта crontab?
подмастерье Компьютерщик
1
Отредактировал вопрос, чтобы объяснить, почему решение crontab не будет работать.
Таави
Нечто подобное date("z") % 3 == 0может пострадать от аналогичной проблемы: условие будет ложным в течение четырех дней с 29 декабря по 3 января, если только этот декабрь не будет частью високосного года.
Рифмоид

Ответы:

12

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

Добавьте эти строки в начало вашего скрипта Bash:

#!/bin/bash

# File that stores the last execution date in plain text:
datefile=/path/to/your/datefile

# Minimum delay between two script executions, in seconds. 
seconds=$((60*60*24*3))

# Test if datefile exists and compare the difference between the stored date 
# and now with the given minimum delay in seconds. 
# Exit with error code 1 if the minimum delay is not exceeded yet.
if test -f "$datefile" ; then
    if test "$(($(date "+%s")-$(date -f "$datefile" "+%s")))" -lt "$seconds" ; then
        echo "This script may not yet be started again."
        exit 1
    fi
fi

# Store the current date and time in datefile
date -R > "$datefile"

# Insert your normal script here:

Не забудьте установить значимое значение datefile=и адаптировать значение seconds=к вашим потребностям ( $((60*60*24*3))оценивается до 3 дней).


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

Чтобы реализовать это, добавьте приведенный ниже фрагмент вверху файла скрипта:

#!/bin/bash

# Minimum delay between two script executions, in seconds. 
seconds=$((60*60*24*3))

# Compare the difference between this script's modification time stamp 
# and the current date with the given minimum delay in seconds. 
# Exit with error code 1 if the minimum delay is not exceeded yet.
if test "$(($(date "+%s")-$(date -r "$0" "+%s")))" -lt "$seconds" ; then
    echo "This script may not yet be started again."
    exit 1
fi

# Store the current date as modification time stamp of this script file
touch -m -- "$0"

# Insert your normal script here:

Опять же, не забудьте адаптировать значение seconds=к вашим потребностям ( $((60*60*24*3))оценивается в 3 дня).

Byte Commander
источник
Да, запись последнего успешного вызова конкретной программы требует некоторого внешнего хранилища данных, и файловая система является очевидным выбором для этого.
Килиан Фот
Не нужно даже сохранять дату - просто дотрагивайтесь до файла каждый раз и проверяйте его отметку времени.
djsmiley2kStaysInside
@ djsmiley2k Да, ты прав. Если риск того, что изменение файла сценария вручную приведет к задержке сброса, является приемлемым, можно также использовать метку времени изменения. Я добавил это к своему ответу.
Byte Commander
Это можно слегка изменить, сохранив текущую временную метку в файл (а не читаемую человеком дату), чтобы вы могли получить истекшее время с помощью $[ $(date +%s) - $(< lastrun) ]. Если скрипт запускается из cron один раз в день, я мог бы добавить некоторый провал к необходимому временному интервалу, чтобы, если выполнение скрипта задерживается на пару секунд, следующий раз не пропустил полный день. Это проверка каждый день, если прошло 71 час.
ilkkachu
Это волшебство с datefile на самом деле сработало, большое спасибо!
Таави
12

Cron действительно не тот инструмент для этого. Там на самом деле широко используются и underloved инструмента под названием в каком могут работать. Он в основном предназначен для интерактивного использования, и я уверен, что кто-то найдет лучший способ сделать это.

В моем случае я бы запустил скрипт, который я запускаю, в testjobs.txt и включил в себя строку, которая гласит:

В качестве примера я хотел бы иметь это как testjobs.txt

echo "cat" >> foo.txt
date >> foo.txt
at now + 3 days < testjobs.txt

У меня есть две невинные команды, которые могут быть вашими сценариями. Я запускаю echo, чтобы убедиться, что у меня есть детерминированный вывод, и дата подтверждения команды запускается при необходимости. Когда at запускает эту команду, она завершает добавление нового задания в at в течение 3 дней. (Я проверял с одной минутой - который работает)

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

Подмастерье
источник
3
+1, как atкажется, лучший подход здесь. Проблема с этим подходом заключается в том, что каждый раз, когда вы вручную запускаете скрипт, вы добавляете другой набор atзадач каждые 3 дня .
Деви Морган
1
+1, но другая проблема (в дополнение к той, на которую указывает @DewiMorgan) состоит в том, что если один сценарий завершится неудачно, все последующие сценарии не будут запущены (если at находится после точки отказа), пока один из них не поймет, что сценарий имел не удалось и перезапустить его. Это может быть плохо, а иногда и хорошо (например, сбой, потому что условия больше нет: хорошо, что он не повторяется через 3 дня?). И каждый раз происходит небольшой дрейф (несколько миллисекунд, если он atнаходится наверху, или, возможно, намного больше, если он atнаходится внизу сценария длительного исполнения)
Оливье Дюлак
Может отправлять электронные письма .... Что может быть решением для сбоя. Возможно, atq и atrm позволят отбирать ошибочные работы? Теоретически вы могли бы написать конкретную дату, но это кажется не элегантным и сложным.
подмастерье Geek
Итак, есть cronjob, который проверяет наличие следующего запуска в at; если его нет, добавьте его на 3 дня и предупредите (или немедленно запустите и проверьте еще раз).
djsmiley2kStaysInside
3

Во-первых, приведенный выше фрагмент кода имеет недопустимый синтаксис Bash, выглядит как Perl. Во-вторых, zпараметр dateзаставляет его выводить числовой часовой пояс. +%jэто номер дня. Тебе нужно:

if [[ ! $(( $(date +%j) % 3 )) ]] ;then
     exit
fi

Но вы все равно увидите странность в конце года:

$ for i in 364 365  1 ; do echo "Day $i, $(( $i % 3 ))"; done
Day 364, 1
Day 365, 2
Day 1, 1

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

waltinator
источник
1
Perl не имеет date()встроенной функции, но это немного похоже на date () в PHP (где z«день года (начиная с 0)»)
ilkkachu
2

Если вы можете просто оставить скрипт запущенным постоянно, вы можете сделать:

while true; do

[inert code here]

sleep 259200
done

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

mkingsbu
источник
Почему нет while true?
Джонатан Леффлер
Хах! Хороший улов. Я не нуждался в том, чтобы использовать это в течение долгого времени, я забыл, что это существовало LOL
Mkingsbu
2
Как atрешение, это будет дрейфовать временем выполнения сценария при каждом запуске. Конечно, это можно обойти, сэкономив время, когда скрипт запускается и спит до 3 дней.
ilkkachu
2

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

Anacron может использоваться для периодического выполнения команд с частотой, указанной в днях. В отличие от cron (8), он не предполагает, что машина работает непрерывно. Следовательно, его можно использовать на машинах, которые не работают 24 часа в сутки, для контроля ежедневных, еженедельных и ежемесячных заданий, которые обычно контролируются cron.

При выполнении Anacron считывает список заданий из файла конфигурации, обычно это / etc / anacrontab (см. Anacrontab (5)). Этот файл содержит список заданий, которые контролирует Anacron. Каждая запись задания указывает период в днях, задержку в минутах, уникальный идентификатор задания и команду оболочки.

Для каждого задания Anacron проверяет, было ли это задание выполнено за последние n дней, где n - период, указанный для этого задания. Если нет, Anacron запускает команду оболочки задания, ожидая количество минут, указанное в качестве параметра задержки.

После выхода из команды Anacron записывает дату в специальный файл метки времени для этого задания, чтобы он мог знать, когда его выполнить снова. Только дата используется для расчета времени. Час не используется.

Twinkles
источник
Хорошо, я обязательно проверю Anacron. Интересно, почему это так недооценивают, если он может творить такую ​​магию
Таави
Реальный вопрос: почему cron не был заменен чем-то лучшим? fcron существует, и я думаю, что systemd работает над собственным решением, но я не в курсе текущего положения дел.
Мерцает