ONLamp.com    
 Published on ONLamp.com (http://www.onlamp.com/)
 See this if you're having trouble printing code examples


FreeBSD Basics

Getting Cron to Do Our Bidding

09/27/2000

Wouldn't it be great if you could get your FreeBSD system to automatically perform maintenance tasks on a regular basis and e-mail you the results? Well, thanks to the built-in cron daemon and the periodic scripts, your FreeBSD system is already doing this for you. In today's article, we'll concentrate on FreeBSD's daemon to execute scheduled commands and learn how to configure the cron daemon to run your own commands and scripts.

Like most other daemons, cron is started for you when you boot into FreeBSD and continues to quietly run in the background. To prove to yourself that cron is indeed running, look for the word cron in the results of the process status command like so:

ps -ax | grep cron
   97  ??  Is     0:07.71 cron

In this example, cron is running with a process ID of 97.

The cron daemon "wakes up" every minute and checks the crontabs (short for chron or time tables) to see if it needs to start the execution of a command or script. A crontab is simply a file containing a list of commands and the time you want each command to start running. When you installed FreeBSD, a system crontab was created for you. You should not make any changes to this crontab file; later on in the article, we'll use the crontab utility to make user crontabs that cron will read in addition to the system crontab.

The system crontab is stored in the /etc directory. We'll use the more command to safely view this file without risk of editing it. You'll want to be logged in to two virtual terminals as we'll be reading the system crontab file at one terminal, and trying other commands at the other terminal. At the first virtual terminal, type:

more /etc/crontab

which should result in this output:

# /etc/crontab - root's crontab for FreeBSD
#
# $FreeBSD: src/etc/crontab,v 1.21 1999/12/15 
17:58:29 obrien Exp $
#
SHELL=/bin/sh
PATH=/etc:/bin:/sbin:/usr/bin:/usr/sbin
HOME=/var/log
#
#minute	hour	mday	month	wday	who	command
#
*/5	*	*	*	*	root	/usr/libexec/atrun

As in any file, remarks begin with a #. The most useful remark is the labeling of the fields: Note that there are five time fields, a user field, and a command field. While the syntax may seem strange at first, each uncommented line in the crontab simply tells cron when you want it to run a specified command.

The following table shows the valid values for the five time fields:

Field

Valid Values

minute

0-59

hour

0-23

dayofmonth

1-31

month

1-12 or first three letters of each month (not case-sensitive)

dayofweek

0-7 (where both 0 and 7 represent Sunday) or first three letters of each day (not case-sensitive)

Values can be a single number or three-letter word, or values can be a range: For example, 1-5 in the dayofweek field represents Monday to Friday. Values can also be separated by a comma: 1,15,30 in the dayofmonth field will run a command on the 1st, 15th, and 30th of each month.

All five time fields accept the * wildcard, which means "use every valid value" for that field. For example, to run a command every month, put a * in the month field.

You can also use the */number value. For example a */2 in the month field means "run every second month." The system crontab has an example of this:

#minute	hour	mday	month	wday	who	command
*/5	*	*	*	*	root	/usr/libexec/atrun

This line is read as: Every minute that is divisible by five (or every five minutes) of every hour of every day of every month, run the atrun command as root. If you don't know what the atrun command does, type this at your other virtual terminal:

whatis atrun
atrun(8) - run jobs queued for later execution

And if that doesn't satisfy your curiosity:

man 8 atrun

One final table before continuing with /etc/crontab: You can replace all five time fields with one string value like this:

String

What it Represents

@reboot

only runs the command when you reboot

@yearly

replaces 0 0 1 1 *

@annually

same as yearly

@monthly

replaces 0 0 1 * *

@weekly

replaces 0 0 * * 0

@daily

replaces 0 0 * * *

@midnight

same as daily

@hourly

replaces 0 * * * *

Return to the first virtual terminal to continue reading through /etc/crontab:

# rotate log files every hour, if necessary
0	*	*	*	*	root	newsyslog

Again, the comment is useful: Every minute that is 0 of every hour of every day of every month, run the newsyslog command. And what does the newsyslog command do?

whatis newsyslog

newsyslog(8) - maintain system log files to 
  manageable sizes
  
do daily/weekly/monthly maintenance
59 1 * * * root  periodic daily   2>&1 | sendmail root
30 3 * * 6 root  periodic weekly  2>&1 | sendmail root
30 5 1 * * root  periodic monthly 2>&1 | sendmail root

Notice that the daily/weekly/monthly maintenance scripts use scattered times. The daily script runs every morning at 1:59 A.M.; if you sleep anywhere near your FreeBSD box, you may have heard your hard drive churning at this strange hour. The weekly script runs every Saturday morning at 3:30 A.M., and the monthly script runs on the first day of every month at 5:30 A.M. It is a good thing to run maintenance scripts during times when your computer's processor should be free (e.g., the middle of the night), and to not run the scripts all at the same time to prevent your processor from getting bogged down.

The 2>&1 symbol tells the Bourne shell to save all of the output and error messages together. These are then piped to the sendmail program to be sent to the root account; this means that all output and error messages from running these scripts will be mailed to root as an e-mail message.

Where does cron find the periodic scripts? At your second virtual terminal, try this:

ls /etc/periodic
daily     monthly     weekly 
ls -F /etc/periodic/weekly
300.uucp*     330.catman*       310.locate*		
340.noid*     120.clean-kvmdb*  320.whatis*  
999.local*

The -F to the ls command puts a * after all executables; therefore, /etc/periodic contains subdirectories that contain the scripts that cron runs daily, weekly, and monthly. If you're truly curious, try:

more /etc/periodic/weekly/310.locate

As a side note, if you are learning shell scripting, viewing these built-in scripts provides practice in recognizing proper syntax.

Since the results of these scripts are mailed to root, root can use a mail reader to check for any problems. Normally, you don't configure your favorite e-mail reader for the root account, since you don't want to send and receive e-mail as the root user. However, you do want to be aware of any problems cron finds. Become the superuser and open up your favorite e-mail reader to see the messages sent from cron.

If you've never done this before, you may have hundreds of messages, depending on how long your computer has been turned on. If you always shut off your computer at night, you won't have any, as the scripts all run after midnight.

If you have any messages, you should have at least one called daily run output. If you read this message, you'll see that cron has a lot of work to do every morning at 1:59. You'll also note that the security check was sent in a separate e-mail message called security check output. This message is well worth reading as it contains the results of checking setuid files, uids of 0, passwordless accounts, kernel log messages, failed logins, and refused connections.

Once you've finished reading the messages, you can safely delete them. Don't forget to leave the superuser account when you exit the e-mail reader.

So far, we've looked at the system crontab, which should be left as is. Now we want to look at making your own crontab file to put in the commands that you want cron to execute. By default on a FreeBSD system, any user can create his own crontab file. These crontabs will be stored in /var/cron/tabs. If you:

ls /var/cron/tabs  (must be root to view)

you'll note that this directory is empty by default. Once you create a crontab, it will create a file with the same name as the user. Never edit this file directly using a text editor; instead, use the crontab utility to create or modify your crontab file.

You may be wondering what types of commands or scripts you would want cron to run for you. Ask yourself what types of commands you run regularly, or should run regularly but sometimes forget to run. For example, you may wish to remove old core files or dead.letter files. Perhaps you would like to flush your Netscape cache on a regular basis.

Once you know what you'd like cron to do for you, decide which command or group of commands will accomplish this task. Every entry in a crontab must be one line; It is okay if the line wraps around your screen as long as you don't press enter. If the command is very long, it may be easier to enter the command into a file, or script, and reference the script in crontab. Let's do an example of each.

If I want to find files with certain extensions and remove them, I can enter a command like this (you would enter this all on one line):

find / \( -name "*.core" -or -name "dead.*" \) 
  -print -exec rm -rf {} \;

Let's see if we can interpret this gobblygook. This is all one find statement; find statements always look like this:

find starting_here look_for_this then_do_this

So, when I typed find /, I told find to start looking at root or /.

I then told find to look for files whose names (-name) end with either .core or begin with dead. Because I wanted find to eventually remove both types of these files, I put both names within parentheses. I also had to quote the () with the backslash \ character so the shell wouldn't try to interpret the parentheses.

Once find has found these files, I told it to execute (-exec) the rm -rf command to remove these files. Whenever you use -exec, you must end the find statement with \; or it won't work. The {} tells exec to use whatever find found as the variable to work on.

Now, that wasn't so bad, was it? Let's get even fancier and create a simple shell script to clear the Netscape cache. This script was borrowed from the FreeBSD mailing list archives -- from this message and also this one.

Open up your favorite text editor and type in the following:

#!/bin/sh

#First, double-check that the user is not 
#currently using Netscape
#Then remove the contents of all the subdirectories 
#in the Netscape cache

if ! (`ps wxu $USER | grep -q [n]etscape`)
        then
        echo "Clearing Netscape cache..."
        rm -rf ~/.netscape/cache/*

fi

echo "Exiting...."

Let's pick apart what we just typed in here. All scripts begin with a shebang:

#!

which is followed by the full pathname to the program that will interpret the script. We've just created a Bourne shell script as we've indicated that the sh command (the Bourne shell) will be responsible for interpreting this script with this line:

#!/bin/sh

We then included four comment lines (they begin with the # character) to remind ourselves of the purpose of this script.

Then we get to the actual meat of the script. It is composed of an if statement which begins with "if" and ends with "fi". The first line of the if statement sets a condition that will either be true or false:

if ! (`ps wxu $USER | grep -q [n]etscape`)

The ! is the not operator, which tells if to only activate the then clause if what is in the () is not true. Basically, use the ps command to search for the first instance of Netscape for this user. If there is no process ID dealing with Netscape, continue the script by echoing the text enclosed in quotes:

echo "Clearing Netscape cache..."

and by clearing the Netscape cache in that user's home directory:

rm -rf ~/.netscape/cache/*

However, if there is a process ID dealing with Netscape, leave the if statement instead of executing the then clause.

Save your file; I saved mine as clean. You can call your script whatever you want, just don't give it a name that is already being used by another command. To double-check, try this in another virtual terminal:

whereis -b potential_scriptname

If your answer is a pathname, there already is a command with that name, so try another name. If all you get is an echo like this:

whereis -b clean
clean:

it is probably safe to use that name for your script.

Once you've saved your script, you still need to use the chmod command to make it executable:

chmod +x clean

It is also a good idea to create a bin directory in your home directory to store your scripts:

cd
mkdir bin
mv clean ~/bin

Finally, you should test that your script actually works before telling cron to use it. If you are in the bin directory, type:

./clean

If you're in any other directory, type:

clean

If you are in the C shell and receive a "Command not found" message, type:

rehash

and try again.

If you receive an error message when your script executes, you most likely have a typo somewhere in your file. Look for the typo and try running your script again until it runs without any error messages.

Now we're ready to create a crontab file to tell cron to run our script and our find command. Log in as a regular user; I'm logged in as the user genisis. Now type:

crontab -e

to invoke the crontab utility in editor mode. If you're real quick, you'll see this message before entering vi:

crontab: no crontab for genisis - using an empty one

Since we're in vi, press ESC then i to enter insert mode; then enter the following text. Make sure you do not press the Enter key when you type in the find command; vi will wrap the text for you to fit your screen.

#every morning at 4:32 search and 
#destroy all core or dead files
32 4 * * * find / \( -name "*.core" -or -name "dead.*" \) -print
-exec rm -rf {} \;

#run the script that clears the Netscape 
#cache every morning at 2:48
48 2 *  *  *  ~genisis/bin/clean

Note that the syntax is slightly different than in the system crontab file as there is no "who" field. Since this crontab will have the same name as the user, the "who" will be the user who made the crontab. When you are finished, double-check for typos, then press ESC and then wq to save your changes and quit. If you input an invalid value in the time fields, crontab will complain and ask if you want to re-enter the editor. Say yes, and look for your typo. Otherwise, you should see this message:

crontab: installing new crontab

Tomorrow when you check your e-mail, you will see two e-mails from cron with the results of your crontab entries. If your commands were successful, they should look like this:

From genisis@.istar.ca. Thu Sep 14 04:38:31 2000
Date: Thu, 14 Sep 2000 04:38:50 -0400 (EDT)
From: genisis (Cron Daemon)
To: genisis
Subject: cron <genisis@istar.ca> find / \( -name "*.core" -or -name "dead.*" \) -print -exec rm -rf {} \;

find: /usr/games/hide: Permission denied
/usr/home/genisis/dead.letter
/usr/home/genisis/netscape.bin.core
find: /var/spool/opielocks: Permission denied
find: /var/cron/tabs: Permission denied
find: /var/games/hackdir: Permission denied
find: /stand/etc: Permission denied
find: /etc/isdn: Permission denied
find: /etc/uucp: Permission denied
find: /root/mail: Permission denied


From genisis@.istar.ca. Thu Sep 14 02:51:36 2000
Date: Thu, 14 Sep 2000 02:52:01 -0400 (EDT)
From: genisis (Cron Daemon)
To: genisis
Subject: cron <genisis@istar.ca> ~genisis/bin/clean

Clearing Netscape cache...
Exiting....

A few final notes on crontabs: if you ever want to view your crontab, you can type:

crontab -l

If you want to make changes to your crontab, use crontab -e again.

Only root can see which users have installed crontabs. Become the superuser and try this:

ls /var/cron/tabs
genisis

You should see a new entry with the same name as the user who just created the crontab.

We've just scratched the surface of the functionality of crontabs. In later articles, as we discover new commands and learn to write more scripts, we'll have additional entries to add to our crontab files.

Next week, we'll delve into the fine art of digging for the useful information concealed in manpages.

Dru Lavigne is a network and systems administrator, IT instructor, author and international speaker. She has over a decade of experience administering and teaching Netware, Microsoft, Cisco, Checkpoint, SCO, Solaris, Linux, and BSD systems. A prolific author, she pens the popular FreeBSD Basics column for O'Reilly and is author of BSD Hacks and The Best of FreeBSD Basics.


Read more FreeBSD Basics columns.

Discuss this article on Daemon News.

Return to the BSD DevCenter.

 

Copyright © 2009 O'Reilly Media, Inc.