Проверьте, запущен ли скрипт по расписанию через cron, а не вручную.

Вопрос или проблема

Устанавливает ли 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

Ссылки:

Метод 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, он выполняется в неинтерактивной оболочке, что оставляет ряд возможностей для различения.

Пример

  1. Использование переменных окружения
    Вы можете вручную установить переменную окружения в crontab. Например, добавьте строку CRON="TRUE" перед вызовом Вашего скрипта. Затем в скрипте проверьте эту переменную:

    if [ "$CRON" == "TRUE" ]; then
       echo "Скрипт запущен из cron"
    else
       echo "Скрипт запущен вручную"
    fi
  2. Определение по имени процесса
    В 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 до интроспекции процесса — предоставляющих возможности кроссплатформенного и надежного определения способа вызова скрипта. Эти подходы не только функциональные, но и достаточно гибкие, чтобы быть адаптированными под различные операционные среды и потребности пользователей.

Оцените материал
Добавить комментарий

Капча загружается...