16

I want to test if cron's mailing stuff after an executed task and things like that work. Is there a more elegant way to test this, then setting a cron job to run every few seconds? So, is there any way to simulate the execution of a cron job / start a cron job manually, but with the same behaviour it would have when being run automatically by cron?

Rui F Ribeiro
  • 56,709
  • 26
  • 150
  • 232
me.at.coding
  • 3,117

3 Answers3

11

The main difference between executing a cron job on the command line and inside cron is its environment. Other potential differences include the current directory, the availability of a terminal, and which shell is used.

This is a fairly accurate simulation of running a cron job via cron. To avoid differences due to the shell, put your job in a script and put only the path to that script in the crontab. Note that the exact set and values of environment variables passed by cron are implementation-dependent; check the crontab(5) man page on your system. ifne is a command that runs the command specified in arguments if it receives some input; there's one in moreutils.

env -i HOME="$HOME" LOGNAME="$LOGNAME" PATH=/usr/bin:/bin SHELL=/bin/sh USER="$USER" \
    /path/to/script </dev/null 2>&1 |
ifne mail -r "Cron Daemon" -s "Cron <$USER@$(hostname)> /path/to/script" "$USER"

Another way of creating a cron-like setting is through at. But at takes care of running the command with the environment variables that the at process received, so you still need to emulate this part.

echo /path/to/script |
env -i HOME="$HOME" LOGNAME="$LOGNAME" PATH=/usr/bin:/bin SHELL=/bin/sh USER="$USER" \
    at now
4

Here is how I did it to simulate the cron execution of a script I've created years ago but which started failing recently.

I created a script called dumpenv:

#!/bin/sh

base=$(basename $0)

env -0 > /tmp/$base.$$.dump

This dumps all environment variables into a file in /tmp/. Then I modified my crontab to add:

*/1 * * * *  [path to newly created script]

This makes the script run every minute. So after a minute, I got a dump of the environment seen by tasks run from cron. If the crontab line is left to run more than a minute, it creates a bunch of identical files with names differing only in the pid part of the file name ($$). No biggie. I could have coded for this eventuality so as to get just one file but the main principle here is "good enough", and I might want to use dumpenv in other contexts where multiple dumps are useful. At any rate, it is advisable to remove the crontab line that was added so as to avoid populating /tmp with lots of junk.

Then I created an additional file which I called /tmp/command which contained the command I wanted to execute, on a single line terminated by a null character. The null character is necessary.

Then I issued:

cat [path to dumpfile crated earlier] /tmp/command | xargs -0 -x env -i

And this replicated the fault I saw when my command is executed from cron. What xargs does is build an command of the form:

env -i [list of environment variables] [command to execute]

and executes it. The list of environment variables comes from the dump file. The command that env executes comes from the /tmp/command file.

The -0 arguments to env and xargs and the required null character I mention above is to prevent environment mangling while variables are being passed around. In cases where the environment contains no environment variable with a newline in it, -0 could conceivably be omitted from the invocation of env in the dumpenv script and from the xargs command, and the /tmp/command file would not require a null character as a line terminator.

I do no explicit redirection of stdin to /dev/null because xargs does it for me. I also do not care about redirecting stdout/stderr, and I do not want to receive an email if the command fails. For cases where these features are desired, Gilles' answer provides the means to do it.

Louis
  • 160
1

Louis' answer is great. I propose a slight possible improvement.

Instead of creating a file /tmp/command which contains the path to the script you want to execute as Louis suggested in the following code:

cat [path to dumpfile crated earlier] /tmp/command | xargs -0 -x env -i

Instead you can name the script directly in the command via the following alternate command (take note of the \x00 appended to the cron script path, which is essential:

printf "/path/to/cron/script\x00" | cat /path/to/dumpfile/created/earlier - | xargs -0 -x env -i

This avoids any need to create a file /tmp/command.