2

I have the following cron job setup on 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}" 

When it runs, it writes the following to /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 

As you can see, the $PATH variable is not preserved by exec.

This is a contrived, simplified example of my actual problem, which is with running pyenv from a cron job. If I change my cron.d file to this:

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 

Then I get this written to the output file:

/opt/pyenv/libexec/pyenv-exec: line 24: pyenv-version-name: command not found 

It correctly executes /opt/pyenv/shims/python. That's just a bash script that runs pyenv exec python --version. It correctly executes /opt/pyenv/bin/pyenv, which is a symlink to /opt/pyenv/libexec/pyenv, which is a bash script that modifies $PATH to include /opt/pyenv/libexec (and yes, it does export it!) and executes /opt/pyenv/libexec/pyenv-exec, which is another bash script that tries to do PYENV_VERSION="$(pyenv-version-name)" on line 24 which results in the error above, because /opt/pyenv/libexec isn't in $PATH. I've narrowed it down to the simplified example above. The exact same pyenv setup with only environment variables and without the shell integration works just fine when not run from cron.

FYI, there's no sudo anywhere in this, and I can reproduce it as other users too. So it doesn't seem to be related to secure_path in /etc/sudoers.

2
  • 2
    Try exec /usr/bin/env bash -x ~/test2.sh. Maybe it reads some bashrc file. Depending on how it's configured at build time, bash will read the bashrc when not interactive under some conditions, and some of those conditions involve $SHLVL which exec here would have an influence on.CommentedNov 25, 2024 at 6:45
  • @StéphaneChazelas It was because I had $BASH_ENV set to a file that was constantly resetting my $PATH. I answered my own question below.
    – jnrbsn
    CommentedNov 27, 2024 at 15:31

1 Answer 1

4

I'm going to answer my own question since I figured it out.

The problem was that I had a custom $BASH_ENV set. When invoked non-interactively, bash doesn't read .profile, .bash_profile, or .bashrc (docs). This is a common problem with cron jobs because they won't have the environment many people expect. However, you can use $BASH_ENV. When bash is invoked non-interactively, it behaves as if it did this:

if [[ -n "$BASH_ENV" ]]; then source "$BASH_ENV"; fi 

So, in an attempt to have a consistent environment regardless of whether my scripts were run from cron or not, I had my $BASH_ENV pointed at a file that looked like this:

source /etc/profile source ~/.profile 

So, my cron jobs would get the same environment as if they were invoked from a login shell. There were two problems with this. First, /etc/profile was defining$PATH instead of adding to it. Second, I didn't realize that $BASH_ENV is loaded for every sub-shell, so my $PATH was constantly getting reset for every sub-shell.

So, I changed my /etc/profile to check for specific paths in $PATH and add them if they're not there. And I changed my $BASH_ENV file to this:

# if this file was already loaded, don't do anything [[ -n "$BASH_ENV_LOADED" ]] && return # load the environment that a login shell would normally get source /etc/profile source ~/.profile # indicate that this file has been loaded export BASH_ENV_LOADED=1 

(I probably shouldn't use a variable name that starts with BASH_, but I couldn't think of what to call it.)

    You must log in to answer this question.

    Start asking to get answers

    Find the answer to your question by asking.

    Ask question

    Explore related questions

    See similar questions with these tags.