How to simulate the environment cron executes a script with?

BashScriptingCron

Bash Problem Overview


I normally have several problems with how cron executes scripts as they normally don't have my environment setup. Is there a way to invoke bash(?) in the same way cron does so I could test scripts before installing them?

Bash Solutions


Solution 1 - Bash

Add this to your crontab (temporarily):

* * * * * env > ~/cronenv

After it runs, do this:

env - `cat ~/cronenv` /bin/sh

This assumes that your cron runs /bin/sh, which is the default regardless of the user's default shell.

Footnote: if env contains more advanced config, eg PS1=$(__git_ps1 " (%s)")$, it will error cryptically env: ": No such file or directory.

Solution 2 - Bash

Cron provides only this environment by default :

  • HOME user's home directory
  • LOGNAME user's login
  • PATH=/usr/bin:/usr/sbin
  • SHELL=/usr/bin/sh

If you need more you can source a script where you define your environment before the scheduling table in the crontab.

Solution 3 - Bash

Couple of approaches:

  1. Export cron env and source it:

Add

    * * * * * env > ~/cronenv

to your crontab, let it run once, turn it back off, then run

    env - `cat ~/cronenv` /bin/sh

And you are now inside a sh session which has cron's environment

  1. Bring your environment to cron

You could skip above exercise and just do a . ~/.profile in front of your cron job, e.g.

    * * * * * . ~/.profile; your_command

3. Use screen

Above two solutions still fail in that they provide an environment connected to a running X session, with access to dbus etc. For example, on Ubuntu, nmcli (Network Manager) will work in above two approaches, but still fail in cron.

    * * * * * /usr/bin/screen -dm

Add above line to cron, let it run once, turn it back off. Connect to your screen session (screen -r). If you are checking the screen session has been created (with ps) be aware that they are sometimes in capitals (e.g. ps | grep SCREEN)

Now even nmcli and similar will fail.

Solution 4 - Bash

You can run:

env - your_command arguments

This will run your_command with empty environment.

Solution 5 - Bash

Depending on the shell of the account

sudo su
env -i /bin/sh

or

sudo su
env -i /bin/bash --noprofile --norc

From http://matthew.mceachen.us/blog/howto-simulate-the-cron-environment-1018.html

Solution 6 - Bash

Answering six years later: the environment mismatch problem is one of the problems solved by systemd "timers" as a cron replacement. Whether you run the systemd "service" from the CLI or via cron, it receives exactly the same environment, avoiding the environment mismatch problem.

The most common issue to cause cron jobs to fail when they pass manually is the restrictive default $PATH set by cron, which is this on Ubuntu 16.04:

"/usr/bin:/bin"

By contrast, the default $PATH set by systemd on Ubuntu 16.04 is:

"/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin"

So there's already a better chance that a systemd timer is going to find a binary without further hassle.

The downside with systemd timers, is there's a slightly more time to set them up. You first create a "service" file to define what you want to run and a "timer" file to define the schedule to run it on and finally "enable" the timer to activate it.

Solution 7 - Bash

Create a cron job that runs env and redirects stdout to a file. Use the file alongside "env -" to create the same environment as a cron job.

Solution 8 - Bash

Don't forget that since cron's parent is init, it runs programs without a controlling terminal. You can simulate that with a tool like this:

http://libslack.org/daemon/

Solution 9 - Bash

By default, cron executes its jobs using whatever your system's idea of sh is. This could be the actual Bourne shell or dash, ash, ksh or bash (or another one) symlinked to sh (and as a result running in POSIX mode).

The best thing to do is make sure your scripts have what they need and to assume nothing is provided for them. Therefore, you should use full directory specifications and set environment variables such as $PATH yourself.

Solution 10 - Bash

The accepted answer does give a way to run a script with the environment cron would use. As others pointed out, this is not the only needed criteria for debugging cron jobs.

Indeed, cron also uses a non-interactive terminal, without an attached input, etc.

If that helps, I have written a script that enables painlessly running a command/script as it would be run by cron. Invoke it with your command/script as first argument and you're good.

This script is also hosted (and possibly updated) on Github.

#!/bin/bash
# Run as if it was called from cron, that is to say:
#  * with a modified environment
#  * with a specific shell, which may or may not be bash
#  * without an attached input terminal
#  * in a non-interactive shell

function usage(){
    echo "$0 - Run a script or a command as it would be in a cron job, then display its output"
    echo "Usage:"
    echo "   $0 [command | script]"
}

if [ "$1" == "-h" -o "$1" == "--help" ]; then
    usage
    exit 0
fi

if [ $(whoami) != "root" ]; then
    echo "Only root is supported at the moment"
    exit 1
fi

# This file should contain the cron environment.
cron_env="/root/cron-env"
if [ ! -f "$cron_env" ]; then
    echo "Unable to find $cron_env"
    echo "To generate it, run \"/usr/bin/env > /root/cron-env\" as a cron job"
    exit 0
fi

# It will be a nightmare to expand "$@" inside a shell -c argument.
# Let's rather generate a string where we manually expand-and-quote the arguments
env_string="/usr/bin/env -i "
for envi in $(cat "$cron_env"); do
   env_string="${env_string} $envi "
done

cmd_string=""
for arg in "$@"; do
    cmd_string="${cmd_string} \"${arg}\" "
done

# Which shell should we use?
the_shell=$(grep -E "^SHELL=" /root/cron-env | sed 's/SHELL=//')
echo "Running with $the_shell the following command: $cmd_string"


# Let's route the output in a file
# and do not provide any input (so that the command is executed without an attached terminal)
so=$(mktemp "/tmp/fakecron.out.XXXX")
se=$(mktemp "/tmp/fakecron.err.XXXX")
"$the_shell" -c "$env_string $cmd_string" >"$so" 2>"$se" < /dev/null

echo -e "Done. Here is \033[1mstdout\033[0m:"
cat "$so"
echo -e "Done. Here is \033[1mstderr\033[0m:"
cat "$se"
rm "$so" "$se"

Solution 11 - Bash

Another simple way I've found (but may be error prone, I'm still testing) is to source your user's profile files before your command.

Editing a /etc/cron.d/ script:

* * * * * user1 comand-that-needs-env-vars

Would turn into:

* * * * * user1 source ~/.bash_profile; source ~/.bashrc; comand-that-needs-env-vars

Dirty, but it got the job done for me. Is there a way to simulate a login? Just a command you could run? bash --login didn't work. It sounds like that would be the better way to go though.

EDIT: This seems to be a solid solution: http://www.epicserve.com/blog/2012/feb/7/my-notes-cron-directory-etccrond-ubuntu-1110/

* * * * * root su --session-command="comand-that-needs-env-vars" user1 -l

Solution 12 - Bash

Answer https://stackoverflow.com/a/2546509/5593430 shows how to obtain the cron environment and use it for your script. But be aware that the environment can differ depending on the crontab file you use. I created three different cron entries to save the environment via env > log. These are the results on an Amazon Linux 4.4.35-33.55.amzn1.x86_64.

1. Global /etc/crontab with root user
MAILTO=root
SHELL=/bin/bash
USER=root
PATH=/sbin:/bin:/usr/sbin:/usr/bin
PWD=/
LANG=en_US.UTF-8
SHLVL=1
HOME=/
LOGNAME=root
_=/bin/env
2. User crontab of root (crontab -e)
SHELL=/bin/sh
USER=root
PATH=/usr/bin:/bin
PWD=/root
LANG=en_US.UTF-8
SHLVL=1
HOME=/root
LOGNAME=root
_=/usr/bin/env
3. Script in /etc/cron.hourly/
MAILTO=root
SHELL=/bin/bash
USER=root
PATH=/sbin:/bin:/usr/sbin:/usr/bin
_=/bin/env
PWD=/
LANG=en_US.UTF-8
SHLVL=3
HOME=/
LOGNAME=root

Most importantly PATH, PWD and HOME differ. Make sure to set these in your cron scripts to rely on a stable environment.

Solution 13 - Bash

In my case, cron was executing my script using sh, which fail to execute some bash syntax. In my script I added the env variable SHELL:

#!/bin/bash
SHELL=/bin/bash

Solution 14 - Bash

I don't believe that there is; the only way I know to test a cron job is to set it up to run a minute or two in the future and then wait.

Attributions

All content for this solution is sourced from the original question on Stackoverflow.

The content on this page is licensed under the Attribution-ShareAlike 4.0 International (CC BY-SA 4.0) license.

Content TypeOriginal AuthorOriginal Content on Stackoverflow
QuestionJorge VargasView Question on Stackoverflow
Solution 1 - BashmmccooView Answer on Stackoverflow
Solution 2 - BashgregsethView Answer on Stackoverflow
Solution 3 - BashCookieView Answer on Stackoverflow
Solution 4 - BashdimbaView Answer on Stackoverflow
Solution 5 - BashtcurdtView Answer on Stackoverflow
Solution 6 - BashMark StosbergView Answer on Stackoverflow
Solution 7 - BashJens CarlbergView Answer on Stackoverflow
Solution 8 - BashRandy ProctorView Answer on Stackoverflow
Solution 9 - BashDennis WilliamsonView Answer on Stackoverflow
Solution 10 - BashDaladimView Answer on Stackoverflow
Solution 11 - Bashfour43View Answer on Stackoverflow
Solution 12 - BashMaikelView Answer on Stackoverflow
Solution 13 - BashSavrigeView Answer on Stackoverflow
Solution 14 - Bashjn80842View Answer on Stackoverflow