Почему exec в bash-скрипте, выполняемом cron, не сохраняет $PATH?

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

У меня настроена следующая задача 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 обрабатывает сессии оболочки и окружение.

  1. Отдельная среда выполнения: При запуске задач через cron каждая задача выполняется в своей отдельной сессии и окружении. Это означает, что любое изменение переменных окружения в одном скрипте не будет передаваться в другой запускаемый скрипт. В вашем случае, когда вы вызываете exec "${HOME}/test2.sh" в test1.sh, вы фактически заменяете текущую оболочку на новую оболочку, создаваемую test2.sh, и не унаследуете изменения в переменных окружения.

  2. Протокол работы cron и SHELL: Вы правильно указали переменную SHELL = /bin/bash в вашем cron-исполнителе. Тем не менее, это не всегда гарантирует, что переменные окружения будут правильно переданы между вызовами скриптов. Cron использует более ограниченную версию исполнения окружения по сравнению с интерактивной сессией оболочки. Поэтому, даже если вы экспортируете переменные окружения, а затем вызываете exec, они могут не быть видны в дочерних процессах, таких как test2.sh.

  3. Проблема с переменной PATH: Как видно из ваших логов, $PATH после выполнения exec в test1.sh не содержит изменение, которое вы сделали, добавив свою пользовательскую директорию (/home/jonathan/mytestdir). Это связано с тем, что переменные окружения, установленные в test1.sh, не передаются в test2.sh из-за функциональности exec. Вместо того чтобы сохранить изменения $PATH, exec фактически создает новое окружение, и его переменные снова устанавливаются в дефолтные, что мы и наблюдаем.

  4. Решения и рекомендации:

    • Вместо использования exec, рассмотрите возможность использования source или . (точка) для выполнения второго скрипта. Это позволит перенаправить любые изменения переменных окружения в текущую сессию:
      . "${HOME}/test2.sh"
    • Можно анализировать и передавать необходимые переменные окружения явно, добавляя их в команду выполнения test2.sh, если вы хотите, чтобы они были доступны:
      PATH="$PATH" "${HOME}/test2.sh"
    • Пересмотрите использование автоматизации через cron для скриптов, требующих сложной настройки среды. Возможно, вам стоит рассмотреть использование системных инициализаций (например, systemd) или скриптов оболочки, которые обеспечивают обширную настройку окружения.

Таким образом, при оптимизации ваших скриптов для работы через cron важно учитывать, что управление окружением имеет свои особенности. Измеряя и корректируя, как и какие переменные окружения вы передаете, вы сможете добиться желаемого поведения от ваших cron-задач.

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

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