Unix Power ToolsUnix Power ToolsSearch this book

25.2. Periodic Program Execution: The cron Facility

This article covers two different versions of cron. There are other versions around: Vixie cron, for instance, has some different features and is common in Linux distributions. A variation called anacron doesn't assume (as cron does) that the system is running 24 hours a day; it's especially nice on portable computers. Rather than trying to cover every flavor, this article has information on older, basic crons that should show you some of what to expect in whatever version you have.

cron allows you to schedule programs for periodic execution. For example, you can use cron to call rsync every hour to update your production web site with new articles or to perform any number of other tasks.

With redirection (Section 43.1), cron can send program output to a log file or to any username via email.

NOTE: cron jobs are run by a system program in an environment that's much different from your normal login sessions. The search path (Section 27.6) is usually shorter; you may need to use absolute pathnames for programs that aren't in standard system directories. Be careful about using command aliases, shell functions and variables, and other things that may not be set for you by the system.

25.2.1. Execution Scheduling

The cron system is serviced by the cron daemon ( Section 1.10). What to run and when to run it are specified to cron by crontab entries, which are stored in the system's cron schedule. On older BSD systems, this consists of the files /usr/lib/crontab and /usr/lib/crontab.local; either file may be used to store crontab entries. Both are ASCII files and may be modified with any text editor. Since usually only root has access to these files, all cron scheduling must go through the system administrator. This can be either an advantage or a disadvantage, depending on the needs and personality of your site.

Under many other versions of Unix, any user may add entries to the cron schedule. crontab entries are stored in separate files for each user. The crontab files are not edited directly by ordinary users, but are placed there with the crontab command (described later in this section). [If your system is using Vixie cron, try creating a crontab file for yourself by typing crontab -l. This will create a new file with vi or the editor you've named in the EDITOR environment variable. Each line of this file should contain either a comment or a crontab entry (described below). When you save and exit the editor, your file will be added to the cron spool directory. -- JJ] [In my experience, the current directory during these personal cron jobs is your home directory. If you read a file or redirect output to a file with a relative pathname (Section 31.2), it will probably be in your home directory. Check your system to be sure. -- JP]

crontab entries direct cron to run commands at regular intervals. Each one-line entry in the crontab file has the following format:

mins hrs day-of-month month weekday username cmd     (BSD)
mins hrs day-of-month month weekday cmd              (other)

Spaces separate the fields. However, the final field, cmd, can contain spaces within it (i.e., the cmd field consists of everything after the space following weekday); the other fields must not contain spaces. The username field is used in the original BSD version only and specifies the username under which to run the command. In other versions, commands are run by the user who owns the crontab in which they appear (and for whom it is named).

The first five fields specify the times at which cron should execute cmd. Their meanings are described in Table 25-1.

Table 25-1. crontab entry time fields

Field

Meaning

Range

mins

The minutes after the hour

0-59

hrs

The hour of the day

0-23 (0 = midnight)

day-of-month

The day within a month

1-31

month

The month of the year

1-12

weekday

The day of the week

1-7 (1 = Monday) BSD

   

0-6 (0 = Sunday) System V

These fields can contain a single number, a pair of numbers separated by a dash (indicating a range of numbers), a comma-separated list of numbers and ranges, or an asterisk (*, a wildcard that represents all valid values for that field). Some versions accept strings of letters: for instance, Vixie cron, at least, accepts month and day names instead of numbers.

If the first character in an entry is a hash mark (#), cron will treat the entry as a comment and ignore it. This is an easy way to temporarily disable an entry without permanently deleting it.

Here are some example crontab entries (shown in non-BSD format):

/proc Section 24.9, 2>&1 Section 36.16, \% Section 25.4

0,15,30,45 * * * *  (echo -n '   '; date; cat /proc/loadavg) >/dev/console
0,10,20,30,40,50 7-18 * * * /usr/lib/atrun
7 0 * * *  find / -name "*.bak" -type f -atime +7 -exec rm {} \;
12 4 * * *  /bin/sh /usr/adm/ckdsk >/usr/adm/disk.log 2>&1
22 2 * * *  /bin/sh /usr/adm/ckpwd 2>&1 | mail root
30 3 * * 1 /bin/csh -f /usr/lib/uucp/uu.weekly >/dev/null 2>&1
12 5 15-21 * * test `date +\%a` = Mon && /usr/local/etc/mtg-notice
#30 2 * * 0,6  /usr/lib/newsbin/news.weekend

The first entry displays the date on the console terminal every 15 minutes (on the quarter hour); notice that multiple commands are enclosed in parentheses to redirect their output as a group. (This runs the commands together in a subshell (Section 43.7).) The second entry runs /usr/lib/atrun every 10 minutes from 7:00 a.m. to 6:50 p.m. daily. The third entry runs a find command at 7 minutes after midnight to remove all .bak files not accessed in 7 days. To cut wear and tear and load on your disk, try to combine find jobs (Section 14.19). Also, as Section 25.8 explains, try not to schedule your jobs at frequently chosen times like 1:00 a.m., 2:00 a.m., and so on; pick oddball times like 4:12 a.m.

The fourth and fifth lines run a shell script every day, at 4:12 a.m. and 2:22 a.m., respectively. The shell to execute the script is specified explicitly on the command line in both cases; the system default shell, usually the Bourne shell, is used if none is explicitly specified. Both lines' entries redirect standard output and standard error, sending it to a file in one case and mailing it to root in the other.

The sixth entry executes a C shell script named uu.weekly, stored in /usr/lib/uucp, at 3:30 a.m. on Monday mornings. Notice that the command format -- specifically the output redirection -- is for the Bourne shell, even though the script itself will be run under the C shell. The seventh entry runs on the third Monday of every month; there's more explanation below. The final entry would run the command /usr/lib/newsbin/news.weekend at 2:30 a.m. on Saturday and Sunday mornings were it not disabled with a #. (# can also be used to add comments to your crontab.)

The fourth through sixth entries illustrate three output-handling alternatives: redirecting it to a file, piping it through mail, and discarding it to /dev/null (Section 43.12). If no output redirection is performed, the output is sent via mail to the user who ran the command.

The cmd field can be any Unix command or group of commands (properly separated with semicolons). The entire crontab entry can be arbitrarily long, but it must be a single physical line in the file.

One problem with the crontab syntax is that it lets you specify any day of the month and any day of the week; but it doesn't let you construct cases like "the third Monday of every month." You might think that the crontab entry:

12 5 15-21 * 1 your-command

would do the trick, but it won't; this crontab entry runs your command on every Monday, plus the 15th through the 21st of each month.[80] An answer from Greg Ubben is shown in the seventh entry. He uses the test (Section 35.26) and date commands to compare the name of today (like Tue) to the day we want the entry to be executed (here, Mon). This entry will be run between the 15th and 21st of each month, but the mtg-notice command will run only on the Monday during that period. The shell's && operator (Section 35.14) runs the mtg-notice command only when the previous test succeeds. Greg actually writes the entry as shown here, testing for failure of the test command:

[80]This strange behavior seems to be a System V peculiarity that somehow infected the rest of the world. Original BSD systems behave the way we explained earlier.

12 5 15-21 * * test `date +\%a` != Mon || /usr/local/etc/mtg-notice

He did it that "backwards" way so the cron job's exit status would be 0 (success) in the case when it doesn't execute mtg-notice. You may need that technique, too.

The cron command starts the cron program. It has no options. Once started, cron never terminates. It is normally started automatically by one of the system initialization scripts. cron reads the crontab file(s) every minute to see whether there have been changes. Therefore, any change to its schedule will take effect within one minute.

25.2.2. A Little Help, etc.

Some flavors of Unix, notably Red Hat and Debian Linux, have included an easy shortcut to creating periodic processes. In some systems, the /etc directory will contain the following directories:

cron.daily

cron.hourly

cron.monthly

cron.weekly

By placing programs and scripts in these directories, you can have those chosen processes occur at the interval designated by the extension of the directory name. By sacrificing granularity of when those processes occur, you gain ease of use. Of course, adding several resource-intensive programs to the same directory may bring an underpowered system to its knees. Excerise care.

In case you're curious, these directories are really just an extension of the Vixie cron system. Looking inside /etc/crontab, we begin to see the magic:

SHELL=/bin/bash
PATH=/sbin:/bin:/usr/sbin:/usr/bin
MAILTO=root
HOME=/

# run-parts
01 * * * * root run-parts /etc/cron.hourly
02 4 * * * root run-parts /etc/cron.daily
22 4 * * 0 root run-parts /etc/cron.weekly
42 4 1 * * root run-parts /etc/cron.monthly

If you want to change when these various cron groups execute, this is the place to make your changes. The run-parts script is a little be more complicated, but it's worth a brief look.

#!/bin/bash

# run-parts - concept taken from Debian

# keep going when something fails
set +e

if [ $# -lt 1 ]; then
        echo "Usage: run-parts <dir>"
        exit 1
fi

if [ ! -d $1 ]; then
        echo "Not a directory: $1"
        exit 1
fi

# Ignore *~ and *, scripts
for i in $1/*[^~,] ; do
        [ -d $i ] && continue
        # Don't run *.{rpmsave,rpmorig,rpmnew,swp} scripts
        [ "${i%.rpmsave}" != "${i}" ] && continue
        [ "${i%.rpmorig}" != "${i}" ] && continue
        [ "${i%.rpmnew}" != "${i}" ] && continue
        [ "${i%.swp}" != "${i}" ] && continue
        [ "${i%,v}" != "${i}" ] && continue

        if [ -x $i ]; then
                $i 2>&1 | awk -v "progname=$i" \
                              'progname {
                                   print progname ":\n"
                                   progname="";
                               }
                               { print; }'
        fi
done

exit 0

The first dozen or so lines of this script are either comments or sanity checks to ensure that it was called with a directory name. The meat of the script is the loop that looks at all the non-tilde files in the given directory. As long as the file isn't a relic from the Red Hat Package Manager or an RCS file, the file is run and its results sent to awk, so that a somewhat clean report can be mailed by cron. You now have the code to set up this system if your Unix doesn't have it.

-- AF, JP, and JJ



Library Navigation Links

Copyright © 2003 O'Reilly & Associates. All rights reserved.