Linux tip: Controlling the duration of scheduled jobs

How to stop a job after a certain time

Say you need to debug a pesky problem by running some traces for 30 minutes at midnight, or you would just like to use your Linux system as an alarm clock. This tip helps you stop jobs, such as those started with the cron and at capabilities, after the jobs have run for a certain time, or when some other criteria are met.

Ian Shields, Senior Programmer, IBM

Ian ShieldsIan Shields works on a multitude of Linux projects for the developerWorks Linux zone. He is a Senior Programmer at IBM at the Research Triangle Park, NC. He joined IBM in Canberra, Australia, as a Systems Engineer in 1973, and has since worked on communications systems and pervasive computing in Montreal, Canada, and RTP, NC. He has several patents and has published several papers. His undergraduate degree is in pure mathematics and philosophy from the Australian National University. He has an M.S. and Ph.D. in computer science from North Carolina State University. You can contact Ian at ishields@us.ibm.com.


developerWorks Contributing author
        level

31 July 2007

Also available in Russian Japanese

Linux® and UNIX® systems allow you to schedule jobs in the future, either just once or on a recurring schedule. A reader of another recent tip, Job scheduling with cron and at, wanted to know how to record a radio or TV program and stop the recording when the program ended. I was reminded of Ettore Bugatti, an Italian who built very fine cars in Alsace-Lorraine. When a customer asked about his use of cable-operated brakes, long after other car makers had switched to hydraulic brakes, Bugatti replied, "Monsieur, I make my cars to go, not to stop." So this tip adds brakes to your job scheduling needs.

Want more Linux tips?

Check out these tips we've compiled so far:

Don't see what you're looking for? Let us know, and we may whip up a tip for you!

Terminating a job after a certain time, or after other criteria are met, usually involves having one process to run the job and another to monitor the completion criteria. In this tip you learn how to have a process manage the time while the real job runs. You also learn how to use the signal and trap facilities to terminate one of these tasks if the other finishes prematurely.

The timer process

The basic tool for timing things in a shell script is the sleep command, which causes the running shell to stop execution for a specified time. The default is to stop for a number of seconds, but you can append the time values with s, m, or h, for seconds, minutes, or hours, respectively. This suspends execution of the shell, so your real work needs to be running in another shell, which you accomplish by placing the task in the background using the & character.

To get started, suppose you want to run a command for 10 minutes. Listing 1 shows a bash shell script that you might code to try running the xclock command for a specified period of time. Try it on your own system.

Listing 1. First attempt using runclock1.sh
#!/bin/bash
runtime=${1:-10m}
# Run xclock in background
xclock&
#Sleep for the specified time.
sleep $runtime
echo "All done"

You should see a clock something like the one in Figure 1.

Figure 1. A plain xclock
A plain xclock

The only thing wrong with this approach is that the script stopped but the clock did not.


Parent, child, and orphan

Listing 2 shows an enhanced runclock2.sh script that captures some information about the process ids of the shell and the xclock processes, along with the output of the script and the output of the ps command, showing the process status for xclock, after the shell completes.

Listing 2. Gathering diagnostic information runclock2.sh
[ian@attic4 ~]$ cat runclock2.sh
#!/bin/bash
runtime=${1:-10m}
mypid=$$
# Run xclock in background
xclock&
clockpid=$!
echo "My PID=$mypid. Clock's PID=$clockpid"
ps -f $clockpid
#Sleep for the specified time.
sleep $runtime
echo "All done"
[ian@attic4 ~]$ ./runclock2.sh 10s
My PID=8619. Clock's PID=8620
UID        PID  PPID  C STIME TTY      STAT   TIME CMD
ian       8620  8619  0 19:57 pts/1    S+     0:00 xclock
All done
[ian@attic4 ~]$ ps -f 8620
UID        PID  PPID  C STIME TTY      STAT   TIME CMD
ian       8620     1  0 19:57 pts/1    S      0:00 xclock

Notice that the parent process id (PPID) in the first output of ps is 8619, which is the process id (PID) of the script. Once the script terminates, the clock process becomes an orphan and is assigned to be a child of the init process—process 1. The child does not terminate immediately when its parent terminates, although it will terminate when you log out of the system.


Terminating a child process

The solution to the problem of non-terminating child processes is to explicitly terminate them using the kill command. This sends a signal to a process, and that usually terminates the process. Later in this tip you see how a process can trap signals and not terminate, but here you use the interrupt signal (SIGINT) to terminate the clock.

To see a list of signals available on your system, use the kill command with the -l option, as shown in Listing 3. Note that some signals are common to all Linux systems, but some may be specific to the particular machine architecture. Some, such as floating point exceptions (SIGFPE) or segment violation (SIGSEGV), are generated by the system, while others such as interrupt (SIGINT), user signals (SIGUSR1 or SIGUSR2), or unconditional terminate (SIGKILL), can be sent by applications.

Listing 3. Signals on a Fedora Core 5 system
[ian@attic4 ~]$ kill -l
 1) SIGHUP       2) SIGINT       3) SIGQUIT      4) SIGILL
 5) SIGTRAP      6) SIGABRT      7) SIGBUS       8) SIGFPE
 9) SIGKILL     10) SIGUSR1     11) SIGSEGV     12) SIGUSR2
13) SIGPIPE     14) SIGALRM     15) SIGTERM     16) SIGSTKFLT
17) SIGCHLD     18) SIGCONT     19) SIGSTOP     20) SIGTSTP
21) SIGTTIN     22) SIGTTOU     23) SIGURG      24) SIGXCPU
25) SIGXFSZ     26) SIGVTALRM   27) SIGPROF     28) SIGWINCH
29) SIGIO       30) SIGPWR      31) SIGSYS      34) SIGRTMIN
35) SIGRTMIN+1  36) SIGRTMIN+2  37) SIGRTMIN+3  38) SIGRTMIN+4
39) SIGRTMIN+5  40) SIGRTMIN+6  41) SIGRTMIN+7  42) SIGRTMIN+8
43) SIGRTMIN+9  44) SIGRTMIN+10 45) SIGRTMIN+11 46) SIGRTMIN+12
47) SIGRTMIN+13 48) SIGRTMIN+14 49) SIGRTMIN+15 50) SIGRTMAX-14
51) SIGRTMAX-13 52) SIGRTMAX-12 53) SIGRTMAX-11 54) SIGRTMAX-10
55) SIGRTMAX-9  56) SIGRTMAX-8  57) SIGRTMAX-7  58) SIGRTMAX-6
59) SIGRTMAX-5  60) SIGRTMAX-4  61) SIGRTMAX-3  62) SIGRTMAX-2
63) SIGRTMAX-1  64) SIGRTMAX

Recall from Listing 2 that we captured the PID of the xclock process using the $! shell variable. Listing 4 shows how to use this information to send a terminate signal (SIGTERM) to the xclock process to terminate it.

Listing 4. Terminating the child process using runclock3.sh
[ian@attic4 ~]$ cat ./runclock3.sh
#!/bin/bash
runtime=${1:-10m}
mypid=$$
# Run xclock in background
xclock&
clockpid=$!
echo "My PID=$mypid. Clock's PID=$clockpid"
ps -f $clockpid
#Sleep for the specified time.
sleep $runtime
kill -s SIGTERM $clockpid
echo "All done"

Listing 5 shows what happens when you execute runclock3.sh. The final kill command confirms that the xclock process (PID 9285) was, indeed, terminated.

Listing 5. Verifying the termination of child processes
[ian@attic4 ~]$ ./runclock3.sh 5s
My PID=9284. Clock's PID=9285
UID        PID  PPID  C STIME TTY      STAT   TIME CMD
ian       9285  9284  0 22:14 pts/1    S+     0:00 xclock
All done
[ian@attic4 ~]$ kill -0 9285
bash: kill: (9285) - No such process

If you omit the signal specification, then SIGTERM is the default signal. The SIG part of a signal name is optional. Instead of using -s and a signal name, you can just prefix the signal number with a -, so the four forms shown in Listing 6 are equivalent ways of killing process 9285. Note that the special value -0, as used in Listing 4 above, tests whether a signal could be sent to a process.

Listing 6. Ways to specify signals with the kill command
kill -s SIGTERM 9285
kill -s TERM 9285
kill -15 9285
kill 9285

Other termination conditions

You now have the basic tools to run a process for a fixed amount of time. Before going deeper into signal handling, let's consider how to handle other termination requirements, such as repetitively capturing information for a finite time, terminating when a file becomes a certain size, or terminating when a file contains a particular string. This kind of work is best done using a loop, such as for, while, or until, with the loop executed repeatedly with some built-in delay provided by the sleep command. If you need finer granularity than seconds, you can also use the usleep command.

You can add a second hand to the clock, and you can customize colors. Use the showrgb command to explore available color names. Suppose you use the command xclock -bg Thistle -update 1& to start a clock with a second hand, and a Thistle-colored background.

Now you can use a loop with what you have learned already to capture images of the clock face every second and then combine the images to make an animated GIF image. Listing 7 shows how to use the xwininfo command to find the window id for the xclock command, then capture 60 clock face images at one second intervals, and finally combine these into an infinitely looping animated GIF file that is 50% of the dimensions of the original clock.

Listing 7. Capturing images one second apart
[ian@attic4 ~]$ cat getclock.sh
#!/bin/bash
windowid=$(xwininfo -name "xclock"| grep '"xclock"' | awk '{ print $4 }')
sleep 5
for n in `seq 10 69`; do
  import -frame  -window $windowid clock$n.gif&
  sleep 1s
#  usleep 998000
done
convert -resize 50% -loop 0 -delay 100 clock?[0-9].gif clocktick.gif
[ian@attic4 ~]$ ./getclock.sh
[ian@attic4 ~]$ file clocktick.gif
clocktick.gif: GIF image data, version 89a, 87 x 96

Timing of this type is always subject to some variation, so the import command to grab the clock image is run in the background, leaving the main shell free to keep time. Nevertheless, some drift is likely to occur because it does take a finite amount of time to launch each subshell for the background processing. This example also builds in a 5-second delay at the start to allow the shell script to be started and then give you time to click on the clock to bring it to the foreground. Even with these caveats, some of my runs resulted in one missed tick and an extra copy of the starting tick because the script took slightly over 60 seconds to run. One way around this problem would be to use the usleep command with a number of microseconds that is enough less than one second to account for the overhead, as shown by the commented line in the script. If all goes as planned, your output image should be something like that in Figure 2.

Figure 2. A ticking xclock
A ticking xclock

This example shows you how to take a fixed number of snapshots of some system condition at regular intervals. Using the techniques here, you can take snapshots of other conditions. You might want to check the size of an output file to ensure it does not pass some limit, or check whether a file contains a certain message, or check system status using a command such as vmstat. Your needs and your imagination are the only limits.


Signals and traps

If you run the getclock.sh script of Listing 7 yourself, and you close the clock window while the script is running, the script will continue to run but will print error messages each time it attempts to take a snapshot of the clock window. Similarly, if you run the runclock3.sh script of Listing 4, and press Ctrl-c in the terminal window where the script is running, the script will immediately terminate without shutting down the clock. To solve these problems, your script needs to be able to catch or trap some of the signals discussed in Terminating a child process.

If you execute runclock3.sh in the background and run the ps -f command while it is running, you will see output similar to Listing 8.

Listing 8. Process information for runclock3.sh
[ian@attic4 ~]$ ./runclock3.sh 20s&
[1] 10101
[ian@attic4 ~]$ My PID=10101. Clock's PID=10102
UID        PID  PPID  C STIME TTY      STAT   TIME CMD
ian      10102 10101  0 06:37 pts/1    S      0:00 xclock
ps -f
UID        PID  PPID  C STIME TTY          TIME CMD
ian       4598 12455  0 Jul29 pts/1    00:00:00 bash
ian      10101  4598  0 06:37 pts/1    00:00:00 /bin/bash ./runclock3.sh 20s
ian      10102 10101  0 06:37 pts/1    00:00:00 xclock
ian      10104 10101  0 06:37 pts/1    00:00:00 sleep 20s
ian      10105  4598  0 06:37 pts/1    00:00:00 ps -f
[ian@attic4 ~]$ All done

[1]+  Done                    ./runclock3.sh 20s

Note that the ps -f output has three entries related to the runclock3.sh process (PID 10101). In particular, the sleep command is running as a separate process. One way to handle premature death of the xclock process or the use of Ctrl-c to terminate the running script is to catch these signals and then use the kill command to kill the sleep command.

There are many ways to accomplish the task of determining the process for the sleep command. Listing 9 shows the latest version of our script, runclock4.sh. Note the following points:

  • The sleep command is explicitly run in the background.
  • The wait command is used to wait for termination of the sleep command.
  • The first trap command causes the stopsleep function to be run whenever a SIGCHLD, SIGINT, or SIGTERM signal is received. The PID of the sleeper process is passed as a parameter.
  • The stopsleep function is run as the result of a signal. It prints a status message and sends the sleep command a SIGINT signal.
  • When the sleep command terminates, for whatever reason, the wait command is satisfied. Traps are then cleared, and the xclock command is terminated.
Listing 9. Trapping signals with runclock4.sh
[ian@attic4 ~]$ cat runclock4.sh
#!/bin/bash

stopsleep() {
  sleeppid=$1
  echo "$(date +'%T') Awaken $sleeppid!"
  kill -s SIGINT $sleeppid >/dev/null 2>&1
}

runtime=${1:-10m}
mypid=$$
# Enable immediate notification of SIGCHLD
set -bm
# Run xclock in background
xclock&
clockpid=$!
#Sleep for the specified time.
sleep $runtime&
sleeppid=$!
echo "$(date +'%T') My PID=$mypid. Clock's PID=$clockpid sleep PID=$sleeppid"
# Set a trap
trap 'stopsleep $sleeppid' CHLD INT TERM
# Wait for sleeper to awaken
wait $sleeppid
# Disable traps
trap SIGCHLD
trap SIGINT
trap SIGTERM
# Clean up child (if still running)
echo "$(date +'%T') terminating"
kill -s SIGTERM $clockpid >/dev/null 2>&1 && echo "$(date +'%T') Stopping $clockpid"
echo "$(date +'%T') All done"

>Listing 10 shows the output from running runclock4.sh three times. The first time, everything runs to its natural completion. The second time, the xclock is prematurely closed. And the third time, the shell script is interrupted with Ctrl-c.

Listing 10. Stopping runclock4.sh in different ways
[ian@attic4 ~]$ ./runclock4.sh 20s
09:09:39 My PID=11637. Clock's PID=11638 sleep PID=11639
09:09:59 Awaken 11639!
09:09:59 terminating
09:09:59 Stopping 11638
09:09:59 All done
[ian@attic4 ~]$ ./runclock4.sh 20s
09:10:08 My PID=11648. Clock's PID=11649 sleep PID=11650
09:10:12 Awaken 11650!
09:10:12 Awaken 11650!
[2]+  Interrupt               sleep $runtime
09:10:12 terminating
09:10:12 All done
[ian@attic4 ~]$ ./runclock4.sh 20s
09:10:19 My PID=11659. Clock's PID=11660 sleep PID=11661
09:10:22 Awaken 11661!
09:10:22 Awaken 11661!
09:10:22 Awaken 11661!
[2]+  Interrupt               sleep $runtime
09:10:22 terminating
09:10:22 Stopping 11660
./runclock4.sh: line 31: 11660 Terminated              xclock
09:10:22 All done

Note how many times the stopsleep function is called as evidenced by the "Awaken" messages. If you are not sure why, you might try making a separate copy of this function for each interrupt type that you catch and see what causes the extra calls.

You will also note that some job control messages tell you about termination of the xclock command and interrupting the sleep command. When you run a job in the background with default bash terminal settings, bash normally catches SIGCHLD signals and prints a message after the next terminal output line is printed. The set -bm command in the script tells bash to report SIGCHLD signals immediately and to enable job control monitoring, The alarm clock example in the next section shows you how to suppress these messages.


An alarm clock

Our final exercise returns to the original problem that motivated this article: how to record a radio program. We will actually build an alarm clock. If your laws allow recording of such material for your proposed use, you can build a recorder instead by adding a program such as vsound.

For this exercise, we will use the GNOME rhythmbox application to illustrate some additional points. Even if you use another media player, this discussion should still be useful.

An alarm clock could make any kind of noise you want, including playing your own CDs, or MP3 files. In central North Carolina, we have a radio station, WCPE, that broadcasts classical music 24 hours a day. In addition to broadcasting, WCPE also streams over the Internet in several formats, including Ogg Vorbis. Pick your own streaming source if you prefer something else.

To start rhythmbox from an X Windows terminal session playing the WCPE Ogg Vorbis stream, you use the command shown in Listing 11.

Listing 11. Starting rhythmbox with the WCPE Ogg Vorbis stream
rhythmbox --play http://audio-ogg.ibiblio.org:8000/wcpe.ogg

The first interesting point about rhythmbox is that the running program can respond to commands, including a command to terminate. So you don't need to use the kill command to terminate it, although you still could if you wanted to.

The second point is that most media players, like the clock that we have used in the earlier examples, need a graphical display. Normally, you run commands with the cron and at facilities at some point when you may not be around, so the usual assumption is that these scheduled jobs do not have access to a display. The rhythmbox command allows you to specify a display to use. You probably need to be logged on, even if your screen is locked, but you can explore those variations for yourself. Listing 12 shows the alarmclock.sh script that you can use for the basis of your alarm clock. It takes a single parameter, which specifies the amount of time to run for, with a default of one hour.

Listing 12. The alarm clock - alarmclock.sh
[ian@attic4 ~]$ cat alarmclock.sh
#!/bin/bash

cleanup () {
  mypid=$1
  echo "$(date +'%T') Finding child pids"
  ps -eo ppid=,pid=,cmd= --no-heading | grep "^ *$mypid"
  ps $playerpid >/dev/null 2>&1 && {
    echo "$(date +'%T') Killing rhythmbox";
    rhythmbox --display :0.0 -quit;
    echo "$(date +'%T') Killing rhythmbox done";
  }
}

stopsleep() {
  sleeppid=$1
  echo "$(date +'%T') stopping $sleeppid"
  set +bm
  kill $sleeppid >/dev/null 2>&1
}

runtime=${1:-1h}
mypid=$$
set -bm
rhythmbox --display :0.0 --play http://audio-ogg.ibiblio.org:8000/wcpe.ogg&
playerpid=$!
sleep $runtime& >/dev/null 2>&1
sleeppid=$!
echo "$(date +'%T') mypid=$mypid player pid=$playerpid sleeppid=$sleeppid"
trap 'stopsleep $sleeppid' CHLD INT TERM
wait $sleeppid
echo "$(date +'%T') terminating"
trap SIGCHLD
trap SIGINT
trap SIGTERM
cleanup $mypid final
wait

Note the use of set +bm in the stopsleep function to reset the job control settings and suppress the messages that you saw earlier with runclock4.sh

Listing 13 shows an example crontab that will run the alarm from 6 a.m. to 7 a.m. each weekday (Monday to Friday) and from 7 a.m. for two hours each Saturday and from 8:30 a.m. for an hour and a half each Sunday.

Listing 13. The alarm clock - alarmclock.sh
0 6 * * 1-6 /home/ian/alarmclock.sh 1h
0 7 * * 7 /home/ian/alarmclock.sh 2h
30 8 * * 0 /home/ian/alarmclock.sh 90m

Refer to our previous tip Job scheduling with cron and at to learn how to set your own crontab for your new alarm clock.

In more complex tasks, you may have several child processes. The cleanup routine shows how to use the ps command to find the children of your script process. You can extend the idea to loop through an arbitrary set of children and terminate each one.


Learn more

If you'd like to know more about administrative tasks in Linux, read the tutorial "LPI exam 102 prep: Administrative tasks,", or see the other Resources below. Don't forget to rate this page and let us know what other tips you'd like to see.

Resources

Learn

Get products and technologies

  • With IBM trial software, available for download directly from developerWorks, build your next development project on Linux.

Discuss

Comments

developerWorks: Sign in

Required fields are indicated with an asterisk (*).


Need an IBM ID?
Forgot your IBM ID?


Forgot your password?
Change your password

By clicking Submit, you agree to the developerWorks terms of use.

 


The first time you sign into developerWorks, a profile is created for you. Information in your profile (your name, country/region, and company name) is displayed to the public and will accompany any content you post, unless you opt to hide your company name. You may update your IBM account at any time.

All information submitted is secure.

Choose your display name



The first time you sign in to developerWorks, a profile is created for you, so you need to choose a display name. Your display name accompanies the content you post on developerWorks.

Please choose a display name between 3-31 characters. Your display name must be unique in the developerWorks community and should not be your email address for privacy reasons.

Required fields are indicated with an asterisk (*).

(Must be between 3 – 31 characters.)

By clicking Submit, you agree to the developerWorks terms of use.

 


All information submitted is secure.

Dig deeper into Linux on developerWorks


static.content.url=http://www.ibm.com/developerworks/js/artrating/
SITE_ID=1
Zone=Linux, AIX and UNIX, Open source
ArticleID=243953
ArticleTitle=Linux tip: Controlling the duration of scheduled jobs
publish-date=07312007