Вопрос или проблема
У меня настроена следующая задача cron на Debian 12:
/etc/cron.d/jonathan-test
:
SHELL=/bin/bash
PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin
* * * * * jonathan /home/jonathan/test1.sh >> /home/jonathan/test.log 2>&1
/home/jonathan/test1.sh
:
#!/usr/bin/env bash
export PATH="/home/jonathan/mytestdir:${PATH}"
echo "test1.sh -> PATH=${PATH}"
export PAAATH="this_is_a_test"
echo "test1.sh -> PAAATH=${PAAATH}"
exec "${HOME}/test2.sh"
/home/jonathan/test2.sh
:
#!/usr/bin/env bash
echo "test2.sh -> PATH=${PATH}"
echo "test2.sh -> PAAATH=${PAAATH}"
Когда он выполняется, он записывает следующее в /home/jonathan/test.log
:
test1.sh -> PATH=/home/jonathan/mytestdir:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin
test1.sh -> PAAATH=this_is_a_test
test2.sh -> PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin
test2.sh -> PAAATH=this_is_a_test
Как видно, переменная $PATH
не сохраняется с помощью exec
.
Это упрощенный пример моей настоящей проблемы, связанной с запуском pyenv из задачи cron. Если я изменю свой файл cron.d
на следующий:
SHELL=/bin/bash
PYENV_ROOT=/opt/pyenv
PATH=/opt/pyenv/shims:/opt/pyenv/bin:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin
* * * * * jonathan python --version >> /home/jonathan/test.log 2>&1
Тогда в выходной файл записывается следующее:
/opt/pyenv/libexec/pyenv-exec: line 24: pyenv-version-name: command not found
Он правильно выполняет /opt/pyenv/shims/python
. Это просто bash-скрипт, который запускает pyenv exec python --version
. Он правильно выполняет /opt/pyenv/bin/pyenv
, который является символической ссылкой на /opt/pyenv/libexec/pyenv
, что является bash-скриптом, который изменяет $PATH
, чтобы включить /opt/pyenv/libexec
(и да, он действительно экспортирует его!) и выполняет /opt/pyenv/libexec/pyenv-exec
, который является еще одним bash-скриптом, пытающимся выполнить PYENV_VERSION="$(pyenv-version-name)"
на 24-й строке, что приводит к вышеуказанной ошибке, потому что /opt/pyenv/libexec
не находится в $PATH
. Я сузил проблему до упрощенного примера выше. Тот же самый pyenv
с только переменными окружения и без интеграции с оболочкой работает нормально, когда не запускается из cron.
Для справки, здесь нет sudo
, и я могу воспроизвести это от имени других пользователей тоже. Поэтому это, похоже, не связано с secure_path
в /etc/sudoers
.
Ответ или решение
Причиной, по которой переменная окружения $PATH
не сохраняется при вызове команды exec
в вашем скрипте bash, запущенном через cron, связано со способом, которым cron обрабатывает сессии оболочки и окружение.
-
Отдельная среда выполнения: При запуске задач через cron каждая задача выполняется в своей отдельной сессии и окружении. Это означает, что любое изменение переменных окружения в одном скрипте не будет передаваться в другой запускаемый скрипт. В вашем случае, когда вы вызываете
exec "${HOME}/test2.sh"
вtest1.sh
, вы фактически заменяете текущую оболочку на новую оболочку, создаваемуюtest2.sh
, и не унаследуете изменения в переменных окружения. -
Протокол работы cron и SHELL: Вы правильно указали переменную
SHELL = /bin/bash
в вашем cron-исполнителе. Тем не менее, это не всегда гарантирует, что переменные окружения будут правильно переданы между вызовами скриптов. Cron использует более ограниченную версию исполнения окружения по сравнению с интерактивной сессией оболочки. Поэтому, даже если вы экспортируете переменные окружения, а затем вызываетеexec
, они могут не быть видны в дочерних процессах, таких какtest2.sh
. -
Проблема с переменной PATH: Как видно из ваших логов,
$PATH
после выполненияexec
вtest1.sh
не содержит изменение, которое вы сделали, добавив свою пользовательскую директорию (/home/jonathan/mytestdir
). Это связано с тем, что переменные окружения, установленные вtest1.sh
, не передаются вtest2.sh
из-за функциональностиexec
. Вместо того чтобы сохранить изменения$PATH
,exec
фактически создает новое окружение, и его переменные снова устанавливаются в дефолтные, что мы и наблюдаем. -
Решения и рекомендации:
- Вместо использования
exec
, рассмотрите возможность использованияsource
или.
(точка) для выполнения второго скрипта. Это позволит перенаправить любые изменения переменных окружения в текущую сессию:. "${HOME}/test2.sh"
- Можно анализировать и передавать необходимые переменные окружения явно, добавляя их в команду выполнения
test2.sh
, если вы хотите, чтобы они были доступны:PATH="$PATH" "${HOME}/test2.sh"
- Пересмотрите использование автоматизации через cron для скриптов, требующих сложной настройки среды. Возможно, вам стоит рассмотреть использование системных инициализаций (например, systemd) или скриптов оболочки, которые обеспечивают обширную настройку окружения.
- Вместо использования
Таким образом, при оптимизации ваших скриптов для работы через cron важно учитывать, что управление окружением имеет свои особенности. Измеряя и корректируя, как и какие переменные окружения вы передаете, вы сможете добиться желаемого поведения от ваших cron-задач.