332

I have a cron job that is scheduled to run everyday, other than changing the schedule, is there any other way to do a test run of the command right now to see if it works as intended?

EDIT: (from the comments) I know the command works fine when enter it in shell (my shell), but I want to know if it works correctly when cron runs it, it could be affected by ENV or shell specific stuff (~ expansion) or ownership and permission stuff or ...

Ali
  • 6,943
  • I don't understand your question? Why not simply run the command? – favadi Jul 10 '12 at 10:45
  • 33
    I know the command works when enter it in shell (my shell), but I want to know if it works when cron runs it, it could be affected by ENV or shell specific stuff (~ expansion) or ownership and permission stuff or ... – Ali Jul 10 '12 at 10:48
  • 3
    So why not create new cron job run every minute with same command? – favadi Jul 10 '12 at 11:12
  • 19
    This is exactly what I ended up doing, but I wondered if there is a way to tell cron you want a test run on job no 7 ! Surely others have had this problem/request/wish before! – Ali Jul 10 '12 at 13:55
  • 7
    Very late to the scene here through google but there was everything wrong about favadi's reply. It was clear he wanted to test it from cron and without editing the crontab specifically to do that. LIttle worse than someone telling you what you want is wrong when they have'nt tried to understand the use case. – RichieHH Sep 13 '18 at 09:12
  • @Ali your question is 100% valid (too bad people were originally so skeptical). Meanwhile, however, please consider changing the accepted answer. – pestophagous Dec 10 '19 at 17:24
  • Thanks @pestophagous yeah, I think there are quite a few good and helpful answers here, still seems like the correct answer to the question is the one accepted. Strictly speaking you cannot make cron itself run the command, you need to run things using some other tool, which kinda defeats the purpose. which one would you say is the correct answer? – Ali Dec 11 '19 at 21:24
  • 1
    @Ali when you put it that way, I understand your rationale (that strictly speaking there is no cron-native way to make cron itself run the task at an improvised time). The answer from Vadim-Sluzky can be useful in many cases, but for those truly-hard-to-pin-down issues, Michael-Barton's answer is more appropriate. I guess if you just "un-select" the current correct one and leave it with no "officially" correct one, then votes alone will allow other things to rise to the top. – pestophagous Dec 11 '19 at 23:13

11 Answers11

146

You can force the crontab to run with following command:

run-parts /etc/cron.daily
Thomas Dickey
  • 76,765
Vadim Sluzky
  • 2,553
  • 23
    ...on the assumption that the OP's cron job (asked 3 years ago) is in cron.daily as opposed to an individual crontab. – Jeff Schaller Nov 24 '15 at 03:49
  • 54
    This does not fully simulate the cron user's environment however, so it is highly likely that you'll still have bugs because once you run your script as an actual cron job your PATH and other envvars may be different than the user you did run-parts /etc/cron.daily as. I am battling this bug right now, as my script will run fine with run-parts but fails when actually run under the cron user. – ArtHare Jun 14 '17 at 15:41
  • This is exactly what I was looking for :D. Need to add this to my admin guide so that I don't forget it again hahah :D – BanjoFox Jan 31 '20 at 18:44
  • 1
    how do i run cron.d/localhost which is an rsnspshot command? – Brian Thomas Sep 11 '20 at 02:20
  • 1
    It is helpful to prefix command with sudo on some installations like Ubuntu. – WinEunuuchs2Unix Oct 21 '20 at 00:22
  • 3
    This does not run the jobs in the correct environment. Also, why would you want to run all the daily jobs just to test run a single job? – Kusalananda Dec 26 '20 at 22:24
  • 1
    @Kusalananda You are correct about the environment. Tonight was the second time I was burned by this answer. So I posted a new answer in Ask Ubuntu. To repeat my comment in October last year, you must prepend sudo to the command. – WinEunuuchs2Unix Aug 11 '21 at 01:33
109

You can simulate the cron user environment as explained in "Running a cron job manually and immediately". This will allow you to test the job works when it would be run as the cron user.


Excerpt from link:


Step 1: I put this line temporarily in the user's crontab:

* * * * *   /usr/bin/env > /home/username/tmp/cron-env

then took it out once the file was written.

Step 2: Made myself a little run-as-cron bash script containing:

#!/bin/bash
/usr/bin/env -i $(cat /home/username/tmp/cron-env) "$@"

So then, as the user in question, I was able to

    run-as-cron /the/problematic/script --with arguments --and parameters
Toby Speight
  • 8,678
  • 1
    Useful trick. Of course, that won't help if you have a percent sign in your command. – basic6 Jul 11 '18 at 07:09
  • Make sure to put the same shell used by the cron in the shebang line of the run-as-cron script. – Roger Collins Apr 23 '20 at 15:50
  • Parsing (i.e., reading and interpreting) the output from env is unreliable and therefore dangerous. Environment variable values can contain any character other than nul; you should handle problematic characters, such as whitespace and quotes. – G-Man Says 'Reinstate Monica' Aug 15 '22 at 21:45
45

As far as I know there is no way to directly do that as cron has a special purpose - running schedules commands at a specific time. So the best thing is to either to manually create a (temporary) crontab entry or write a script which removes and resets the environment.

Explanation of "removes and resets the environment":

A wrapper script could be started with env -i (which removes the environment), which would source a saved environment (making sure to export all variables, possibly by setting set -a first) before starting your script.

The saved environment would be the default environment of a cron job, recorded by running env (or declare -p depending on what shell your cron jobs use) as a cronjob, saving its output.

Kusalananda
  • 333,661
Ulrich Dangel
  • 25,369
  • Parsing the output from env is unreliable and therefore dangerous.  Environment variable values can contain any character other than nul; you would need to handle the standard problematic characters, such as whitespace and quotes. – G-Man Says 'Reinstate Monica' Aug 12 '22 at 21:38
23

CronitorCLI has a command cronitor select that lets you select and run any cron job from the command line. You do not need to create a Cronitor account to use it.

https://cronitor.io/docs/using-cronitor-cli

Here is an example:

ubuntu@ip-10-0-0-112:~$ cronitor select

Use the arrow keys to navigate: ↓ ↑
? Select job to run:
▸ /var/runner/src/bin/batch_reports.py runner.settings.prod
  /var/runner/src/bin/trigger_reports.py runner.settings.prod
  ... etc ...
Shane H
  • 372
7

After having the need myself to debug cron jobs, I wrote the following script.  It tries hard to simulate the exact same conditions as cron before running a command (that includes a modified environment, but it also has to do with non-interactive shells, no attached terminal, etc.).

Call it with your command/script as argument, and you have an instant and painless way to debug your cron job. It is also hosted (and possibly updated) on GitHub: run-as-cron.sh:

#!/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 redirect the output into files

and provide /dev/null as input

(so that the command is executed without an open terminal

on any standard file descriptor)

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
  • 219
  • 2
  • 6
  • The "generate a string" bit is problematic and would be solved in a safer way with the help of env. – Kusalananda Dec 26 '20 at 22:27
  • (1) If you don’t use any bash-isms, you can write your shebang as #!/bin/sh.  But see point 9.  (2) You specifically allow and handle a command and its arguments to be passed as arguments to your run-as-cron script; you should say so in your Usage message.  On the other hand, there’s no real need or reason to talk about a command *or* a script — scripts are commands.  (3) You test $1 for help options, which is good.  You should also check whether it is null (or missing). (4) [ condition₁ -o condition₂ ] is deprecated. … (Cont’d) – G-Man Says 'Reinstate Monica' Aug 12 '22 at 21:35
  • (Cont’d) … Use[ condition₁ ] || [ condition₂ ]. (Or you could use a case statement here.) (5) whoami is probably safe, but you should get into the habit of quoting all command substitutions: e.g., "$(whoami)". (6) You define a variable cron_env, but then you use the literal string /root/cron-env (needlessly) two more times after that. (7) sed is a fairly powerful program; you generally don’t need to combine it with grep. You could set the_shell with$(sed -n '/^SHELL=/ s///p' "$cron_env"). (8) And, after setting the_shell, you should verify that it’s not null. … (Cont’d) – G-Man Says 'Reinstate Monica' Aug 12 '22 at 21:35
  • (Cont’d) …  (9) You don’t actually need the curly braces in the env_string="${env_string} $envi " and cmd_string="${cmd_string} \"${arg}\" " commands.  (They don’t hurt, but you don’t need them.  See ${variable_name} doesn’t mean what you think it does ….)  Also, you could use cmd_string+=… — but note that this is non-POSIX, so, if you use it, you’ll need to keep the #!/bin/bash shebang.  (10) I agree with @Kusalananda: parsing "$@" is problematic and can be avoided.  … (Cont’d) – G-Man Says 'Reinstate Monica' Aug 12 '22 at 21:35
  • (Cont’d) …  (11) Also, parsing the output from env is unreliable and therefore dangerous.  Environment variable values can contain any character other than nul; your code does not handle the standard problematic characters, such as whitespace and quotes.  (12) Running a command with < /dev/null does not actually make it “executed without an attached terminal”.  (13) It might be useful to display the value of $? after the command runs.  … (Cont’d) – G-Man Says 'Reinstate Monica' Aug 12 '22 at 21:37
  • (Cont’d) … (14) If you’re going to use terminal escape sequences, you should define them separately (e.g., bold='\033[1m') as variables with descriptive names, and then use those variables in your echo / printf statement.  Ideally you should get their values dynamically; i.e., from something like tput.  (15) You have not taken into account the special meaning that % has in actual cron command lines.  (Don’t feel too bad; I would have overlooked that myself if not for basic6’s comment.) – G-Man Says 'Reinstate Monica' Aug 12 '22 at 21:37
5

Daladim's answer was useful to me. It runs the command immediately, not after up to 59 seconds later as the "* * * * *" style answers do. It presents the output as soon as the command runs.

I have extended it a bit, adding the ability to run as any user, not just root, fixing some corner case bugs, and adding a specific message with instructions to bootstrap the environment to match cron.

I put it up on GitHub.

Link a command called run-as-cron to this file from your path.

If you have a crontab entry like this:

0 0 * * * command.sh --option

test it like this:

run-as-cron command.sh --option

or as root:

sudo run-as-cron command.sh --option

or as some other user:

sudo su otheruser
run-as-cron command.sh --option

Note, that the very first time you run this as a user you'll need to add a command to crontab -e and wait a minute to grab the environment.

These questions and answers might also be useful:

AdminBee
  • 22,803
poleguy
  • 253
  • (1) Please see my comments on Daladim’s answer; most of them also apply to yours.  (2) It’s good that you added attribution to your script (which is derived from Daladim’s), but it would be better if you also gave the original author credit by name. – G-Man Says 'Reinstate Monica' Aug 13 '22 at 21:48
  • 1
    @G-ManSays'ReinstateMonica' I've added the name Daladim to my github script. I will also look at the other comments you made when I can make time. – poleguy Aug 18 '22 at 15:41
0

If you are auto-generating your crontab you can do something like this:

    cat <<EOF >/tmp/mycrontab
# Run five minutes after midnight, every day - use bash to get PATH to include sbin so that ifconfig will work
5 0 * * *       bash -lc '$HOME/myscript.sh'
# run on restart - use bash to get PATH to include sbin so that ifconfig will work
@reboot         bash -lc '$HOME/myscript.sh'
# Installation time +1 min $(date -d +1min +'%Y-%m-%d %H:%M:%d' ) - use bash to get PATH to include sbin so that ifconfig will work
$(date -d +1min +'%M %H %d %m %w')  bash -lc '$HOME/myscript.sh'
EOF
    crontab /tmp/mycrontab

This will make 'myscript.sh' run after midnight, at reboot and at installation time and properly at the same time 7 years from now if the month, day and weekday align again at that time.

0

Just change the schedule to * * * * *.

That would be the easiest and most reliable solution.

Kolyunya
  • 579
-1

I am quoting answer (view fully) from serverfault:


Execute crontab commands in-bulk :

crontab -l | grep -v '^#' | cut -f 6- -d ' ' | while read CMD; do eval $CMD; done

to run it under another user:

sudo -H -u username bash -c "crontab... "

T.Todua
  • 139
-2

I found a solution that seems to be a little better for my purposes (commands shown for CentOS / RHEL-like, but should be adaptable basically anywhere).

This requires libfaketime - you can build it yourself from source at https://github.com/wolfcw/libfaketime or just use one of the many packages from https://pkgs.org/download/libfaketime.

  1. Stop crond service - service crond stop
  2. Figure out when your service should run by - https://crontab.guru is pretty useful for this.
  3. Run crond in foreground mode through libfaketime's faketime tool (it lets you fake out the syscall for time lookups for any child processes).
    1. I wouldn't run this on a production server
    2. faketime '2019-10-17 07:59:50' /usr/sbin/crond -n -x test,sch
[root@user-crontesting-dvc-01 ~]# faketime '2019-10-17 07:59:50' /usr/sbin/crond -n -x sch
debug flags enabled: sch
[4841] cron started
log_it: (CRON 4841) INFO (Syslog will be used instead of sendmail.)
log_it: (CRON 4841) INFO (RANDOM_DELAY will be scaled with factor 34% if used.)
log_it: (CRON 4841) INFO (running with inotify support)
[4841] GMToff=0
log_it: (CRON 4841) INFO (@reboot jobs will be run at computer's startup.)
[4841] Target time=1571299200, sec-to-wait=11
user [root:0:0:...] cmd="/usr/libexec/myexc/crontesting.cron > /dev/null 2> &1"
[4841] Target time=1571299260, sec-to-wait=60
log_it: (root 4844) CMD (/usr/libexec/myexc/crontesting.cron > /dev/null 2> &1)
log_it: (root 4843) CMDOUT (/bin/bash: -c: line 0: syntax error near unexpected token `&')
log_it: (root 4843) CMDOUT (/bin/bash: -c: line 0: `/usr/libexec/myexc/crontesting.cron > /dev/null 2> &1')
javanix
  • 105
-2

Yea, you type the command and options directly into the CLI and run the job...

/bin/bash /some/script.sh --options

is just

$ ./some/script.sh --options

with a timer.

berndbausch
  • 3,557
John
  • 5
  • 2
    This is incorrect. First, the script to run is /some/script.sh, not ./some/script.sh. While this is probably a mere typo, a more serious problem is the fact that the interactive user's environment is not the same as cron's environment. Several suggestions have already been made how to test the command in the correct environment; please consider deleting your answer. – berndbausch Feb 16 '21 at 00:52
  • 1
    @berndbausch: Maybe not ‘merely’ a typo; there’s a widespread misconception that you need to use ./ to run a script, independent of location. – G-Man Says 'Reinstate Monica' Apr 22 '22 at 17:59
  • Wow, I have never heard of the "requirement" to put a dot at the beginning of a script's pathname. If that's true, it indicates a widespread and deep misunderstanding of UNIX filesystem principles. Let's correct it whenever we encounter it. – berndbausch Apr 24 '22 at 02:43
  • /some/script.sh is an absolute pathname. Meaning: The root directory contains a directory named some, where script.sh resides. ./some/script.sh on the other hand means that directory some is located in the current directory, not root. – berndbausch Apr 24 '22 at 02:46
  • Assuming script.sh is indeed a Bash script, you can run it by handing it over to the interpreter, such as bash /some/script.sh or bash ./some/script.sh. – berndbausch Apr 24 '22 at 02:47
  • 1
    If script.sh has execution permissions, /some/script.sh or ./some/script.sh can be run directly from the command line as-is. To complicate things a little, this assumes either that the interpreter is specified by a "hash-bang" clause in the first line of the script, or that the default shell is able to interpret the script. – berndbausch Apr 24 '22 at 02:52
  • Reviewing comments and it was not mentioned that ./script.sh syntax is used for running scripts in a present directory for a script that is not in one's PATH. It's obviously redundant and not needed to include a preceding . if you specify the absolute directory. – oemb1905 Dec 28 '23 at 00:16