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.
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 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
The only thing wrong with this approach is that the script stopped but the clock did not.
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.
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
|
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
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.
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
sleepcommand is explicitly run in the background. - The
waitcommand is used to wait for termination of thesleepcommand. - The first
trapcommand causes thestopsleepfunction to be run whenever a SIGCHLD, SIGINT, or SIGTERM signal is received. The PID of the sleeper process is passed as a parameter. - The
stopsleepfunction is run as the result of a signal. It prints a status message and sends thesleepcommand a SIGINT signal. - When the
sleepcommand terminates, for whatever reason, thewaitcommand is satisfied. Traps are then cleared, and thexclockcommand 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.
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.
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.
Learn
- This tip expands on our previous tip
Job
scheduling with cron and at,
which introduces you to scheduling jobs on Linux.
- See all
Linux tips on developerWorks.
- Review the tutorial
"LPI
exam 102 prep: Administrative
tasks"
(developerWorks, Jul 2007) for more information on other administrative tasks in
Linux, including user administration, backups, system logs, and Network Time
Protocol. It is part of a larger
LPI
exam prep tutorial series
covering Linux fundamentals and preparing you for system administrator
certification.
- The
Linux
Documentation Project has a
variety of useful documents, especially its HOWTOs.
- In the
developerWorks Linux zone,
find more resources for Linux developers, including
Linux tutorials,
as well as
our
readers' most recent favorite Linux articles and tutorials.
- Stay current with
developerWorks technical events and Webcasts.
Get products and technologies
-
Order the SEK for Linux,
a two-DVD set containing the latest IBM trial software for Linux from DB2®,
Lotus®, Rational®, Tivoli®, and WebSphere®.
- With
IBM trial software,
available for download directly from developerWorks, build your next development
project on Linux.
Discuss
- Participate in the discussion forum.
- Get involved in the
developerWorks community
through our developer blogs, forums, podcasts, and community topics in our new
developerWorks spaces.

Ian 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.





