275

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?

Jorge Vargas
  • 6,712
  • 7
  • 32
  • 29
  • I would suggest this solution: http://unix.stackexchange.com/questions/27289/how-can-i-run-a-cron-command-with-existing-environmental-variables – rudi May 14 '13 at 12:38
  • Taking @gregseth a bit further, I gave this solution: http://unix.stackexchange.com/questions/27289/how-can-i-run-a-cron-command-with-existing-environmental-variables/89596#89596 – Robert Brisita Sep 05 '13 at 22:38
  • A Cron job has many differences with a job run in your usual shell (a modified environment being only *one* of them). I have written an answer below, including a script that mimics all of the differences I have managed to notice. – Daladim Jul 14 '20 at 21:53

14 Answers14

431

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.

Sida Zhou
  • 3,529
  • 2
  • 33
  • 48
mmccoo
  • 8,386
  • 5
  • 41
  • 60
  • 6
    note: if adding that to the global /etc/crontab, you'll need the username too. E.g. * * * * * root env > ~/cronenv – Greg Aug 20 '12 at 14:23
  • 10
    Good, simple idea. For the impatient use '* * * * *' to run next minute, and remember to turn off again when you are done playing ;-) – Mads Buus Mar 12 '13 at 19:26
  • How would you return to the regular bash prompt? – Madsn May 31 '13 at 06:31
  • 5
    @Madsn To return to the previous bash shell try: exit – spkane Jun 26 '13 at 20:43
  • 3
    I saved it to `/tmp/cronenv` to avoid any confusion about what `~` meant in that context. – Max Williams Mar 23 '16 at 15:54
  • 5
    The importance of this answer cannot be underestimated. Worthy of a paragraph inclusion in a book. – Xofo Mar 28 '16 at 05:14
  • Note that this will fail with variables that have a space in the value. Maybe `export -p > file` + `source file`? – tokland Dec 01 '16 at 20:41
  • 1
    is `env - `cat ~/cronenv` /bin/sh` should be written as cron job also? please give an example – JavaSa May 17 '18 at 21:29
  • @JavaSa You run `env - $(cat ~/cronenv) /bin/sh`, and it opens a shell replicating the crontab environment, where you can type in your cronjob command and see if it works as expected. – mwfearnley Sep 08 '20 at 11:16
  • This works wonders, but be aware you can still run into some differences with programs that are aware of whether they're being run in a TTY or not. – Zachary Vance Aug 02 '21 at 20:34
  • 1
    My preferred syntax: `env -i $(cat ~/cronenv) /bin/sh`. `-i` is the same as `-` (--ignore-environment), but less cryptic. `$(...)` is the same as `\`...\`` – wisbucky Aug 27 '21 at 17:53
  • Careful about cron and current working dir when exporting and importing`env`, because if `PWD=...` is set in `env`, then `cd ... && ...` will not work in cron job – Sida Zhou Oct 28 '21 at 03:12
63

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.

Braiam
  • 1
  • 11
  • 47
  • 78
gregseth
  • 12,952
  • 15
  • 63
  • 96
  • 7
    `.` is usually not part of `PATH` anymore, for [security reasons](http://superuser.com/q/156582/2259). – l0b0 May 14 '13 at 12:55
59

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

  2. 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.

Cookie
  • 12,004
  • 13
  • 54
  • 83
  • 2
    My preferred syntax for Option1 is `env -i $(cat ~/cronenv) /bin/sh`. `-i` is the same as `-` (--ignore-environment), but less cryptic. `$(...)` is the same as `\`...\`` – wisbucky Aug 27 '21 at 18:04
22

You can run:

env - your_command arguments

This will run your_command with empty environment.

dimba
  • 26,717
  • 34
  • 141
  • 196
  • 4
    cron doesn't run in a completely empty environment, does it? – jldupont Sep 14 '10 at 12:45
  • 2
    gregseth identified the variables included in the environment by cron. You can include that variables on the command line. $ env - PATH="$PATH" command args – DragonFax Jan 25 '12 at 19:49
  • 5
    @DragonFax @dimba I use `env - HOME="$HOME" LOGNAME="$USER" PATH="/usr/bin:/bin" SHELL="$(which sh)" command arguments` which seems to do the trick – l0b0 May 14 '13 at 12:56
  • This is a great simple method for testing scripts in "hostile" or unknown environments. If you're explicit enough that it'll run in this, it'll run under cron. – Oli Dec 18 '18 at 10:51
16

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

tcurdt
  • 14,518
  • 10
  • 57
  • 72
14

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.

J. Bruni
  • 20,322
  • 12
  • 75
  • 92
Mark Stosberg
  • 12,961
  • 6
  • 44
  • 49
10

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.

3

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/

Randy Proctor
  • 7,396
  • 3
  • 25
  • 26
3

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"
Daladim
  • 198
  • 10
2

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.

Dennis Williamson
  • 346,391
  • 90
  • 374
  • 439
  • This is exactly what I'm trying to solve. We get tons of problems with scripts that assume something by mistake. Doing full paths and setting env variables and all the junk ends up with horrible huge unmaintainable cron lines – Jorge Vargas Apr 07 '10 at 21:26
  • re *sh sorry I grew up with bash = shell so it's hard to me to remember the alternatives (and sometimes better) shells. – Jorge Vargas Apr 07 '10 at 21:29
  • 1
    @Jorge: lines in the crontab should be fairly short. You should do all the setup you need within the script (or a wrapper script). Here's a typical line from a crontab as an example: `0 0 * * 1 /path/to/executable >/dev/null 2>&1` and then, within "executable" I would set values for `$PATH`, etc., and use full directory specs to input and output files, etc. For example: `/path/to/do_something /another/path/input_file /another/path/to/output_file` – Dennis Williamson Apr 07 '10 at 23:15
1

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
four43
  • 1,675
  • 2
  • 20
  • 33
0

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.

Community
  • 1
  • 1
Maikel
  • 514
  • 4
  • 9
0

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
Savrige
  • 3,352
  • 3
  • 32
  • 38
-9

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.

jn80842
  • 1
  • 3