261

I have a #!/bin/bash file in cron.week directory.

Is there a way to test if it works? Can't wait 1 week

I am on Debian 6 with root

M--
  • 25,431
  • 8
  • 61
  • 93
dynamic
  • 46,985
  • 55
  • 154
  • 231
  • 5
    Can't you just test the script by running it on the command line? – Frédéric Hamidi Feb 13 '11 at 14:45
  • 6
    just add a separate entry in your `crontab` that runs your file every few minutes, see if the expected results are there, and then delete the test entry from your crontab. try `crontab -e` – davin Feb 13 '11 at 14:47
  • 68
    No, running it from the command line is not a good way. Just running the script from the command line doesn't guarantee it will even get run as the same user was when run from cron. Even if it does get run as the same user, you can never be sure if everything else will be the same. Scripts that work from the command line can fail for _all_ sorts of reasons when run from cron. – andynormancx Nov 15 '13 at 10:51
  • @andynormancx, you saw `command line can fail for all sorts of reasons when run from cron`. Can you say me at least two? Is not a joke question. Thank you. – m3nda May 08 '14 at 02:53
  • 10
    "all sorts" may be been slightly hyperbolic... permissions and environment variables are the two that spring to mind, you could easily have different other initial conditions like aliases though – andynormancx May 23 '14 at 10:45
  • 4
    I've just been through testing a new cron job, I'll add another item to "all sorts". If you have things in bash_profile they don't get run once in a cron job, whereas testing the job from the command line works perfectly. Testing cron jobs on the standard command line isn't a good test at all. – andynormancx Apr 24 '16 at 14:37

11 Answers11

244

Just do what cron does, run the following as root:

run-parts -v /etc/cron.weekly

... or the next one if you receive the "Not a directory: -v" error:

run-parts /etc/cron.weekly -v

Option -v prints the script names before they are run.

Is Ma
  • 913
  • 11
  • 14
NNRooth
  • 2,804
  • 1
  • 13
  • 3
  • 4
    If I use -v option it showing "Not a directory: -v" error, there is no man page for this command in my system , -v means verbose right? I am using centos 6.4 – max Dec 29 '13 at 11:01
  • 3
    To avoid the "Not a directory" error, I had to do this instead: run-parts /etc/cron.weekly -v – Craig Hyatt Mar 06 '14 at 12:21
  • 25
    But this doesn't take into account the environment variables that could be set in the crontab – fcurella Apr 29 '14 at 21:10
  • why not put an example with your comment @fcurella. not everyone knows what you mean by cron environment variables – Nicholas DiPiazza Jun 15 '14 at 14:49
  • 35
    `run-parts` just runs the scripts of a given directory. Nothing to do with cron. – Nicolas Jun 16 '14 at 07:19
  • 1
    @Nicolas from `man cron`: The files under /etc/cron.d do not need to be executable, while the files under /etc/cron.hourly, /etc/cron.daily, /etc/cron.weekly and /etc/cron.monthly do, as they are run by run-parts (see run-parts(8) for more information). – Anne van Rossum Nov 25 '14 at 08:40
  • This doesn't seem to do anything with crontab run-parts -v /var/spool/cron/crontabs/ – Seth Apr 23 '15 at 20:41
  • This definitely doesn’t work on Debian. I tested it with a cronjob with a percent sign which didn’t throw any errors with the `run-parts` command in the terminal, and didn’t even execute the `root` file in the `crontabs` directory (the percent sign was the initial reason why I landed on this page as I found out that it is a control character for a new line in a crontab and that you have to escape it). – Rafael Bugajewski Jan 13 '16 at 09:41
  • As fcurella already pointed out: It would help to add that 1.) The environment,specified in the crontab should be loaded. 2.) run-parts should be executed as 'root', so that the root-environment is used, not the user-environment. – Alex Mar 23 '16 at 10:10
  • 26
    This really shouldn't have been upvoted and accepted, beyond running the script it does nothing to tell you whether the script will actually work when run from cron. Use the excellent crontest script in one of the other answers to this question. – andynormancx Apr 24 '16 at 14:38
  • I have noticed output results are not recorded in cron's /var/log/cron. The output is at `/var/spool/mail/(user)`. Interesting links: http://superuser.com/questions/306163/what-is-the-you-have-new-mail-message-in-linux-unix http://unix.stackexchange.com/questions/176229/where-to-find-the-crontab-logs-in-centos – Junior Mayhé Jul 04 '16 at 18:38
102

A wee bit beyond the scope of your question... but here's what I do.

The "how do I test a cron job?" question is closely connected to "how do I test scripts that run in non-interactive contexts launched by other programs?" In cron, the trigger is some time condition, but lots of other *nix facilities launch scripts or script fragments in non-interactive ways, and often the conditions in which those scripts run contain something unexpected and cause breakage until the bugs are sorted out. (See also: https://stackoverflow.com/a/17805088/237059 )

A general approach to this problem is helpful to have.

One of my favorite techniques is to use a script I wrote called 'crontest'. It launches the target command inside a GNU screen session from within cron, so that you can attach with a separate terminal to see what's going on, interact with the script, even use a debugger.

To set this up, you would use "all stars" in your crontab entry, and specify crontest as the first command on the command line, e.g.:

* * * * * crontest /command/to/be/tested --param1 --param2

So now cron will run your command every minute, but crontest will ensure that only one instance runs at a time. If the command takes time to run, you can do a "screen -x" to attach and watch it run. If the command is a script, you can put a "read" command at the top to make it stop and wait for the screen attachment to complete (hit enter after attaching)

If your command is a bash script, you can do this instead:

* * * * * crontest --bashdb /command/to/be/tested --param1 --param2

Now, if you attach with "screen -x", you'll be facing an interactive bashdb session, and you can step through the code, examine variables, etc.

#!/bin/bash

# crontest
# See https://github.com/Stabledog/crontest for canonical source.

# Test wrapper for cron tasks.  The suggested use is:
#
#  1. When adding your cron job, use all 5 stars to make it run every minute
#  2. Wrap the command in crontest
#        
#
#  Example:
#
#  $ crontab -e
#     * * * * * /usr/local/bin/crontest $HOME/bin/my-new-script --myparams
#
#  Now, cron will run your job every minute, but crontest will only allow one
#  instance to run at a time.  
#
#  crontest always wraps the command in "screen -d -m" if possible, so you can
#  use "screen -x" to attach and interact with the job.   
#
#  If --bashdb is used, the command line will be passed to bashdb.  Thus you
#  can attach with "screen -x" and debug the remaining command in context.
#
#  NOTES:
#   - crontest can be used in other contexts, it doesn't have to be a cron job.
#       Any place where commands are invoked without an interactive terminal and
#       may need to be debugged.
#
#   - crontest writes its own stuff to /tmp/crontest.log
#
#   - If GNU screen isn't available, neither is --bashdb
#

crontestLog=/tmp/crontest.log
lockfile=$(if [[ -d /var/lock ]]; then echo /var/lock/crontest.lock; else echo /tmp/crontest.lock; fi )
useBashdb=false
useScreen=$( if which screen &>/dev/null; then echo true; else echo false; fi )
innerArgs="$@"
screenBin=$(which screen 2>/dev/null)

function errExit {
    echo "[-err-] $@" | tee -a $crontestLog >&2
}

function log {
    echo "[-stat-] $@" >> $crontestLog
}

function parseArgs {
    while [[ ! -z $1 ]]; do
        case $1 in
            --bashdb)
                if ! $useScreen; then
                    errExit "--bashdb invalid in crontest because GNU screen not installed"
                fi
                if ! which bashdb &>/dev/null; then
                    errExit "--bashdb invalid in crontest: no bashdb on the PATH"
                fi

                useBashdb=true
                ;;
            --)
                shift
                innerArgs="$@"
                return 0
                ;;
            *)
                innerArgs="$@"
                return 0
                ;;
        esac
        shift
    done
}

if [[ -z  $sourceMe ]]; then
    # Lock the lockfile (no, we do not wish to follow the standard
    # advice of wrapping this in a subshell!)
    exec 9>$lockfile
    flock -n 9 || exit 1

    # Zap any old log data:
    [[ -f $crontestLog ]] && rm -f $crontestLog

    parseArgs "$@"

    log "crontest starting at $(date)"
    log "Raw command line: $@"
    log "Inner args: $@"
    log "screenBin: $screenBin"
    log "useBashdb: $( if $useBashdb; then echo YES; else echo no; fi )"
    log "useScreen: $( if $useScreen; then echo YES; else echo no; fi )"

    # Were building a command line.
    cmdline=""

    # If screen is available, put the task inside a pseudo-terminal
    # owned by screen.  That allows the developer to do a "screen -x" to
    # interact with the running command:
    if $useScreen; then
        cmdline="$screenBin -D -m "
    fi

    # If bashdb is installed and --bashdb is specified on the command line,
    # pass the command to bashdb.  This allows the developer to do a "screen -x" to
    # interactively debug a bash shell script:
    if $useBashdb; then
        cmdline="$cmdline $(which bashdb) "
    fi

    # Finally, append the target command and params:
    cmdline="$cmdline $innerArgs"

    log "cmdline: $cmdline"


    # And run the whole schlock:
    $cmdline 

    res=$?

    log "Command result: $res"


    echo "[-result-] $(if [[ $res -eq 0 ]]; then echo ok; else echo fail; fi)" >> $crontestLog

    # Release the lock:
    9<&-
fi
Stabledog
  • 3,110
  • 2
  • 32
  • 43
  • 2
    Awesome, the perfect solution. All the other techniques I'd tried didn't really work, crontest solved the problem easily. – andynormancx Apr 24 '16 at 14:34
  • 1
    This was way more than I needed but +1 for a great solution anyway; setting the cron to run more often and dump the output to /tmp was good enough to spot my issue. – jcollum Aug 16 '16 at 16:02
  • 2
    Cool. Yeah, no reason to fire up the bulldozer if you just need a hand spade :) – Stabledog Aug 17 '16 at 19:34
  • 2
    Great stuff. Note on mac I had to `brew tap discoteq/discoteq; brew install flock` and then modify the script to use `/usr/local/bin/flock` – Claudiu Apr 23 '17 at 19:06
  • 1
    nice utility! Very useful to diagnose authentication issues in non interactive contexts. – Eric Blum Sep 13 '18 at 22:19
  • Why don't the crontab lines contain a user in the sixth column? – problemofficer - n.f. Monica Sep 06 '19 at 01:15
  • bashdb is not longer part of Debian/Ubuntu. It needs to be installed manually if one wants to debug a script. – Arigion Sep 18 '22 at 15:07
  • The crontest script does not handle chaining scripts. If you have a crontab like `script1.sh && script2.sh` only script1 is executed. – Arigion Sep 18 '22 at 15:10
53

After messing about with some stuff in cron which wasn't instantly compatible I found that the following approach was nice for debugging:

crontab -e

* * * * * /path/to/prog var1 var2 &>>/tmp/cron_debug_log.log

This will run the task once a minute and you can simply look in the /tmp/cron_debug_log.log file to figure out what is going on.

It is not exactly the "fire job" you might be looking for, but this helped me a lot when debugging a script that didn't work in cron at first.

evandrix
  • 6,041
  • 4
  • 27
  • 38
Automatico
  • 12,420
  • 9
  • 82
  • 110
  • 9
    I would recommend this approach. The problem in most cases is that you don't see the error. – Yauhen Yakimovich Feb 03 '15 at 15:01
  • 9
    Incorrect line, not any distribution uses bash for cron shell and so &>> doesn't work. I suggest to use ```>>/tmp/cron_debug_log.log 2>&1``` Instead – drizzt May 01 '16 at 18:45
  • 1
    What do you mean by `/path/to/prog` -- which prog? – Nathan Jun 24 '16 at 00:53
  • 4
    The program you are going to run with cron. This can be `usr/home/myFancyScript.sh` or it can be a simple `ls`, or whatever you want to run regularly. – Automatico Jun 24 '16 at 11:27
  • This worked for me. It was the easiest method to do a quick test to check if everything is fine – abhijit Aug 26 '19 at 23:53
25

I'd use a lock file and then set the cron job to run every minute. (use crontab -e and * * * * * /path/to/job) That way you can just keep editing the files and each minute they'll be tested out. Additionally, you can stop the cronjob by just touching the lock file.

    #!/bin/sh
    if [ -e /tmp/cronlock ]
    then
        echo "cronjob locked"
        exit 1
    fi

    touch /tmp/cronlock
    <...do your regular cron here ....>
    rm -f /tmp/cronlock
dave fernholz
  • 409
  • 4
  • 4
  • 4
    You can also use `flock(1)` from the shell; see `man 1 flock`. Quite handy for such uses since it provides automatic blocking. – Craig Ringer Nov 16 '12 at 02:24
5

What about putting it into cron.hourly, waiting until the next run of hourly cron jobs, then removing it? That would run it once within an hour, and in the cron environment. You can also run ./your_script, but that won't have the same environment as under cron.

Jeremiah Willcock
  • 30,161
  • 7
  • 76
  • 78
4

None of these answers fit my specific situation, which was that I wanted to run one specific cron job, just once, and run it immediately.

I'm on a Ubuntu server, and I use cPanel to setup my cron jobs.

I simply wrote down my current settings, and then edited them to be one minute from now. When I fixed another bug, I just edited it again to one minute from now. And when I was all done, I just reset the settings back to how they were before.

Example: It's 4:34pm right now, so I put 35 16 * * *, for it to run at 16:35.

It worked like a charm, and the most I ever had to wait was a little less than one minute.

I thought this was a better option than some of the other answers because I didn't want to run all of my weekly crons, and I didn't want the job to run every minute. It takes me a few minutes to fix whatever the issues were before I'm ready to test it again. Hopefully this helps someone.

Ben C
  • 395
  • 3
  • 8
  • Why would you use cron to run "just once, and run it immediately"? It sounds like you'd be better off just running the script directly. – Ian Hunter Jun 23 '20 at 21:20
4

Aside from that you can also use:

http://pypi.python.org/pypi/cronwrap

to wrap up your cron to send you an email upon success or failure.

Muhammad Lukman Low
  • 8,177
  • 11
  • 44
  • 54
  • 10
    Doesn't Cron already email and output from the script upon execution? – Erik Aug 10 '13 at 04:20
  • Cron emails the user, and typically the user for most cron jobs is either root or a non-interactive service only user, so you never see the emails. This is how servers get filled when an error triggers a huge flood of emails and they bounce to root. – dragon788 Oct 17 '17 at 18:44
3

The solution I am using is as follows:

  1. Edit crontab(use command :crontab -e) to run the job as frequently as needed (every 1 minute or 5 minutes)
  2. Modify the shell script which should be executed using cron to prints the output into some file (e.g: echo "Working fine" >>
    output.txt)
  3. Check the output.txt file using the command : tail -f output.txt, which will print the latest additions into this file, and thus you can track the execution of the script
Abhilash
  • 339
  • 2
  • 7
2

I normally test by running the job i created like this:

It is easier to use two terminals to do this.

run job:

#./jobname.sh

go to:

#/var/log and run 

run the following:

#tailf /var/log/cron

This allows me to see the cron logs update in real time. You can also review the log after you run it, I prefer watching in real time.

Here is an example of a simple cron job. Running a yum update...

#!/bin/bash
YUM=/usr/bin/yum
$YUM -y -R 120 -d 0 -e 0 update yum
$YUM -y -R 10 -e 0 -d 0 update

Here is the breakdown:

First command will update yum itself and next will apply system updates.

-R 120 : Sets the maximum amount of time yum will wait before performing a command

-e 0 : Sets the error level to 0 (range 0 - 10). 0 means print only critical errors about which you must be told.

-d 0 : Sets the debugging level to 0 - turns up or down the amount of things that are printed. (range: 0 - 10).

-y : Assume yes; assume that the answer to any question which would be asked is yes

After I built the cron job I ran the below command to make my job executable.

#chmod +x /etc/cron.daily/jobname.sh 

Hope this helps, Dorlack

Dorlack
  • 21
  • 2
2
sudo run-parts --test /var/spool/cron/crontabs/

files in that crontabs/ directory needs to be executable by owner - octal 700

source: man cron and NNRooth's

n611x007
  • 8,952
  • 8
  • 59
  • 102
  • 5
    Two tings. First: To run things as sudo uses a different environment (defined variables). So bugs in your crontab could work when executed as 'sudo', but wont work when executed as 'root' (and vice versa). Second: 'man cron' does not show me this info (ok, my man seems to be debian-specific) ... 'man run-parts' says that --test does not run the scripts, but only prints the names of them. So the scripts are not even tested. A minor thing: 'executable by owner': As far as I know, cronjobs only are executed as root. – Alex Mar 23 '16 at 09:55
  • @Alex `As far as I know, cronjobs only are executed as root.` - depends. in the directory mentioned above, every user can have a crontab, and they will be executed as the user who created them. Cronjobs in `/etc/cron.d` always need a user specified as whom they run (see `man cron`). – xeruf Aug 24 '20 at 21:28
-1

I'm using Webmin because its a productivity gem for someone who finds command line administration a bit daunting and impenetrable.

There is a "Save and Run Now" button in the "System > Scheduled Cron Jobs > Edit Cron Job" web interface.

It displays the output of the command and is exactly what I needed.

Kickaha
  • 3,680
  • 6
  • 38
  • 57