Вопрос или проблема
Устанавливает ли cron какие-либо переменные при запуске программы? Если скрипт запущен cron, я хотел бы пропустить некоторые части; в противном случае, вызвать эти части.
Как я могу узнать, запущен ли Bash-скрипт cron?
Мне ничего не известно о том, чтобы cron
по умолчанию как-то менял среду, но есть несколько вещей, которые вы можете сделать, чтобы достичь желаемого эффекта.
1) Создайте жесткую или символическую ссылку на файл скрипта, чтобы, например, myscript
и myscript_via_cron
указывали на один и тот же файл. Внутри скрипта вы можете проверить значение $0
, когда хотите условно запустить или опустить определенные части кода. Поместите соответствующее имя в ваш crontab, и вы готовы.
2) Добавьте опцию в скрипт и установите эту опцию в вызове crontab. Например, добавьте опцию -c
, которая сообщит скрипту запустить или опустить соответствующие части кода, и добавьте -c
к имени команды в вашем crontab.
И конечно, cron может устанавливать произвольные переменные среды, так что вы можете просто добавить строку типа RUN_BY_CRON="TRUE"
в ваш crontab и проверить её значение в вашем скрипте.
Скрипты, запускаемые cron, не запускаются в интерактивных оболочках. Также это касается стартовых скриптов. Различие в том, что интерактивные оболочки имеют STDIN и STDOUT, подключенные к tty.
Метод 1: проверьте, включен ли в $-
флаг i
. i
устанавливается для интерактивных оболочек.
case "$-" in
*i*)
interactive=1
;;
*)
not_interactive=1
;;
esac
Метод 2: проверьте, пустой ли $PS1
.
if [ -z "$PS1" ]; then
not_interactive=1
else
interactive=1
fi
Ссылки:
- https://www.gnu.org/software/bash/manual/html_node/Special-Parameters.html
- https://www.gnu.org/software/bash/manual/html_node/Is-this-Shell-Interactive_003f.html
Метод 3: проверьте ваш tty. Это не настолько надежно, но для простых задач cron вы должны быть в порядке, так как cron по умолчанию не выделяет tty для скрипта.
if [ -t 0 ]; then
interactive=1
else
non_interactive=1
fi
Имейте в виду, что вы все же можете принудительно использовать интерактивную оболочку, используя -i
, но вы бы, вероятно, знали, если бы делали это…
Сначала получите PID cron, затем получите родительский PID (PPID) текущего процесса и сравните их:
CRONPID=$(ps ho %p -C cron)
PPID=$(ps ho %P -p $$)
if [ $CRONPID -eq $PPID ] ; then echo Cron is our parent. ; fi
Если ваш скрипт запущен другим процессом, который мог быть запущен cron, вы можете вернуться назад по родительским PID, пока не дойдете до $CRONPID или 1 (PID init).
что-то вроде этого, возможно (Непроверенно-Но-Может-Сработает<TM>):
PPID=$$ # начнем с текущего PID
CRON_IS_PARENT=0
CRONPID=$(ps ho %p -C cron)
while [ $CRON_IS_PARENT -ne 1 ] && [ $PPID -ne 1 ] ; do
PPID=$(ps ho %P -p $PPID)
[ $CRONPID -eq $PPID ] && CRON_IS_PARENT=1
done
От Deian:
Это версия, протестированная на RedHat Linux
# начнем с текущего PID
MYPID=$$
CRON_IS_PARENT=0
# это может вернуть список из нескольких PID
CRONPIDS=$(ps ho %p -C crond)
CPID=$MYPID
while [ $CRON_IS_PARENT -ne 1 ] && [ $CPID -ne 1 ] ; do
CPID_STR=$(ps ho %P -p $CPID)
# родительский PID оказался строкой с ведущими пробелами
# это преобразует его в int
CPID=$(($CPID_STR))
# теперь переберите CRON PID и сравните их с CPID
for CRONPID in $CRONPIDS ; do
[ $CRONPID -eq $CPID ] && CRON_IS_PARENT=1
# можно было бы завершить раньше, но это также нормально
done
done
# теперь делайте все, что хотите с этой информацией
if [ "$CRON_IS_PARENT" == "1" ]; then
CRON_CALL="Y"
else
CRON_CALL="N"
fi
echo "CRON Call: ${CRON_CALL}"
Если ваш файл скрипта вызывается cron
и в его первой строке указана оболочка, например #!/bin/bash
, вам нужно найти имя родителя-родителя для вашей цели.
1) cron
вызывается в указанное время в вашем crontab
, выполняя оболочку
2) оболочка выполняет ваш скрипт
3) ваш скрипт выполняется
Родительский PID доступен в bash как переменная $PPID
. Команда ps
для получения родительского PID родительского PID:
PPPID=`ps h -o ppid= $PPID`
но нам нужно имя команды, а не pid, поэтому вызываем
P_COMMAND=`ps h -o %c $PPPID`
теперь нам просто нужно проверить результат на “cron”
if [ "$P_COMMAND" == "cron" ]; then
RUNNING_FROM_CRON=1
fi
Теперь вы можете проверять где угодно в вашем скрипте
if [ "$RUNNING_FROM_CRON" == "1" ]; then
## делайте что-то при запуске из cron
else
## делайте что-то при запуске из оболочки
fi
Удачи!
Нет авторитетного ответа. Некоторые другие ответы здесь фактически пытаются сопоставить среду cron, что является непростой задачей.
Лично я просто устанавливаю переменную в своем crontab, например, CRON='in_cron'
вверху. Затем вы можете проверить это следующим образом:
if [ "$CRON" != "in_cron" ]; then
echo "Это не задание cron"
fi
Это однозначно различает интерактивные/nohup/ssh и cron, но требует объявления переменной в crontab соответствующего пользователя. Для скриптов /etc/cron.d вы должны быть в состоянии поместить его в /etc/crontab (что, по крайней мере, работает на моей системе Debian 12).
Переменная терминала ($TERM
) здесь довольно неплохая, как и флаг опции “интерактивный” в оболочке (специальный параметр $-
, содержащий i
). Некоторые системы устанавливают TERM=dumb
, в то время как большинство оставляет его пустым, так что мы просто проверим оба и также проверим флаг опции интерактивности:
if [ "${TERM:-dumb}$-" != "dumb${-#*i}" ]; then
echo "Это не задание cron"
fi
Вышеприведенный код подставляет слово “dumb”, когда для $TERM
нет значения. Таким образом, условие выполняется, когда нет $TERM
или $TERM
установлен в “dumb”, или если переменная $PS1
не пуста, или если $-
не соответствует самому себе при удалении всех символов до первой i
(это не удаляет ничего, когда нет i
).
Я протестировал это на Debian 9, 11 и 12 (TERM=
), CentOS 6.4 и 7.4 (TERM=dumb
) и FreeBSD 7.3 и 11.2 (TERM=
). Ранее это дополнялось $PS1
, но теперь мой cron на Debian 12 как-то устанавливает эту переменную.
По крайней мере на Debian 12 это различает интерактивные/nohup и cron/ssh.
В качестве альтернативы можно проверить имя терминала. Cron обычно (проверьте это!) не выделяет терминал, так что вы можете запустить tty
и стандарт POSIX требует, чтобы (если у него действительно нет терминала) он сказал not a tty
в своем выводе:
if [ "$(tty)" != "not a tty" ]; then
echo "Это не задание cron"
fi
По крайней мере на Debian 12 это будет различать интерактивные и cron/ssh/nohup.
Еще один ответ упоминал ( : > /dev/tty) 2>/dev/null
для проверки интерактивности.
По крайней мере на Debian 12 это будет различать интерактивные/nohup с cron/ssh.
Четвертым способом воспользоваться тем, что cron не выделяет терминал, будет просто проверить, открыт ли стандартный ввод. Вы можете сделать это с помощью ! [ -t 0 ]
, но это даст вам некорректный ответ, если вы передаете данные в скрипт через конвейер.
Работает на FreeBSD или Linux:
if [ "Z$(ps o comm="" -p $(ps o ppid="" -p $$))" == "Zcron" -o \
"Z$(ps o comm="" -p $(ps o ppid="" -p $(ps o ppid="" -p $$)))" == "Zcron" ]
then
echo "Вызвано из cron"
else
echo "Не вызвано из cron"
fi
Можно подняться по дереву процессов насколько пожелаете.
Простая команда echo $TERM | mail [email protected]
в cron показала мне, что как в Linux, так и в AIX, cron, похоже, устанавливает $TERM
в ‘dumb’.
Теоретически могут все еще существовать настоящие тупые терминалы, но я подозреваю, что в большинстве случаев это должно быть достаточно…
Эта функция bash
должна работать на системах как с cron
, так и с crond
.
Протестировано под Debian bullseye.
## Возвращает 0 (успех), если мы выполняемся под Cron
function undercron ()
{
local cronpid=$(pgrep --uid=root --oldest --exact '^crond?$')
for ((ppid=PPID; ppid > 1; ppid=$(ps ho %P -p $ppid))); do
if ((ppid == cronpid)); then
return 0
fi
done
return 1
}
Общее решение вопроса “является ли мой вывод терминалом или я выполняюсь из скрипта” такое:
( : > /dev/tty ) 2>/dev/null && dev_tty_good=y || dev_tty_good=n
Есть проблема, о которой я не вижу упоминаний. Большинство тестов не удается, когда я запускаю свой скрипт с удаленного компьютера через ssh!
ssh [email protected] ./SmartHome.sh
Здесь (например) [ -t 0 ] и ( : > /dev/tty) и $TERM все указывают неправильно (ОК, они все на самом деле правы, но не указывают на то, что нам нужно).
Итак, самое простое универсальное решение – это вызвать скрипт из cron с аргументом, как упоминалось в самом оцененном ответе в пункте 2). Тогда ЛЮБОЙ другой вызов будет восприниматься как ручной запуск. Я использую – (но это может быть что угодно) как первый аргумент для вызова cron и затем проверяю его в своем скрипте:
[ "$1" == "-" ] && shift || Run_Manually=1
Я протестировал несколько ОС (Ubuntu, CentOS, SUSE) и выполнил скрипт, который включает printenv
через SSH и локальные терминалы, и похоже, что эти переменные никогда не доступны, если crond
выполняет скрипт, но доступны во всех других случаях:
LESSOPEN
LESSCLOSE
Таким образом, проверка, если эта переменная не установлена, должна работать:
if [[ -z ${LESSOPEN+x} ]]; then
echo "Скрипт выполняется cron"
else
echo "Скрипт выполняется вручную"
fi
.
Ответ или решение
Определение того, был ли скрипт запущен через cron или вручную, является важной задачей, особенно когда скрипт должен выполнять разные действия в зависимости от способа запуска. На данный момент cron, как правило, не устанавливает какие-либо специфические переменные окружения, которые указывали бы на его запуск, что вынуждает нас искать альтернативные решения.
Теория
Cron — это планировщик задач в Unix-системах, автоматически запускающий скрипты и команды по заданию расписания. Одним из его ключевых отличий является то, что он запускает задачи в среде с ограниченным набором доступных переменных окружения, не так как при интерактивном запуске. Когда скрипт запускается cron, он выполняется в неинтерактивной оболочке, что оставляет ряд возможностей для различения.
Пример
-
Использование переменных окружения
Вы можете вручную установить переменную окружения в crontab. Например, добавьте строкуCRON="TRUE"
перед вызовом Вашего скрипта. Затем в скрипте проверьте эту переменную:if [ "$CRON" == "TRUE" ]; then echo "Скрипт запущен из cron" else echo "Скрипт запущен вручную" fi
-
Определение по имени процесса
В Unix-системах cron не выделяет терминал. Можно использовать командуtty
, чтобы проверить наличие терминала:if [ "$(tty)" == "not a tty" ]; then echo "Запуск через cron" else echo "Ручной запуск" fi
Применение
Основная задача распределения действий в вашем скрипте в зависимости от контекста запуска может быть решена несколькими способами:
-
Установка именованного флага или переменной: Подход с ручной установкой переменной окружения в crontab наиболее прост и надежен, так как не требует сложных проверок и может быть быстро внедрен в уже существующие скрипты. Настройте crontab так, чтобы он экспортировал переменную
CRON
, и в вашем скрипте используйте проверку этой переменной для принятия соответствующих решений. -
Проверка наличия терминала: Использование команды
tty
позволяет достоверно определить, был ли выделен терминал для выполнения скрипта. Хотя из этого нельзя однозначно сделать вывод, что скрипт запущен cron, часто этого достаточно, так как большинство случаев запуска без терминала происходит через планировщики задач. -
Проверка пользователя или процесса-родителя (PPID):
Расширенный подход — это определение процесса-родителя через командыps
и сопоставление его с PID cron. Если скрипт был запущен в результате другого скрипта, который был сгенерирован cron, можно еще раз вернуться назад по цепочке PID до идентификации crontab как источника.
Таким образом, мы видим сочетание методов — от настройки crontab до интроспекции процесса — предоставляющих возможности кроссплатформенного и надежного определения способа вызова скрипта. Эти подходы не только функциональные, но и достаточно гибкие, чтобы быть адаптированными под различные операционные среды и потребности пользователей.