5 whenjobs - A powerful but simple cron replacement
9 Editing the jobs script:
14 Get and set variables:
16 whenjobs --get variable
17 whenjobs --set variable value [--type bool|int|float|string]
20 Start and stop the per-user daemon:
22 whenjobs --daemon-start
23 whenjobs --daemon-stop
24 whenjobs --daemon-status
25 whenjobs --daemon-restart
30 whenjobs --cancel serial
31 whenjobs --start "name"
35 Whenjobs is a powerful but simple replacement for cron. It lets you
36 run jobs periodically like cron, but it also lets you trigger jobs to
37 run when user-defined variables are set or change value.
39 Periodic jobs are written like this:
43 # Get the current load average.
44 load=`awk '{print $1}' /proc/loadavg`
45 whenjobs --set load $load --type float
48 When-statements let you create jobs that run based on variables set
53 mail -s "ALERT: high load average: $load" $LOGNAME < /dev/null
56 (When statements are "edge-triggered", meaning that this job will only
57 run when the load goes from under 6 to E<ge> 6).
59 Like L<crontab(5)>, whenjobs are controlled by a jobs file which can
60 be edited from the command line:
64 Whenjobs uses a daemon called L<whenjobsd(8)>. Unlike crond, this
65 daemon runs as the same user. Each user who wants to use whenjobs
66 starts their own daemon:
68 $ whenjobs --daemon-start
70 You can also have the daemon start as you when the machine boots by
71 adding the following line to a boot file such as C</etc/rc.local>.
72 Replace C<username> with your username:
74 su username -c /usr/sbin/whenjobsd
76 Variables are the key to expressing dependencies between whenjobs.
77 Variables are stored (per-user) in the daemon. You can use the
78 command line tool to examine and set variables:
80 $ whenjobs --variables
82 $ whenjobs --set cat sushi
86 The act of setting a variable (using I<--set>) can trigger jobs to run.
92 =item B<--cancel> serial
94 Cancel the job with the given serial number.
96 Use I<--jobs> to list running jobs along with their serial numbers.
97 The serial number is also available in the job script (as
98 C<$JOBSERIAL>) and in the log file.
100 =item B<--daemon-start>
102 =item B<--daemon-stop>
104 Start and stop the per-user daemon.
106 =item B<--daemon-status>
108 Prints the status of the daemon: C<up> or C<down>.
110 =item B<--daemon-restart>
112 Restart the daemon. (If it is not running, then this command
119 Edit the jobs script. If you make changes to the jobs script, then it
120 is automatically uploaded to the daemon.
122 The C<$EDITOR> environment variable is used for editing. If not set,
125 =item B<--get> variable
127 Print the value of a variable.
131 List all running jobs.
133 Note that it is possible for the same job to be running more than once
134 (for example, a periodic job that takes longer than the period to run).
140 List the jobs script.
142 =item B<--lib> directory
144 Set the library directory which needs to contain the auxiliary files
145 C<pa_when.cmo> and C<whenlib.cma>. Normally you do not need to
146 specify this. However if you are running whenjobs without installing
147 it, then you need to point this to the C<lib/> directory from the
150 whenjobs --lib $builddir/lib -e
152 =item B<--set> variable value
154 =item B<--type> bool|int|float|string|unit
156 I<--set> sets the variable named C<variable> to the new C<value>. The
157 variable is created if it does not already exist. Note that setting a
158 variable can cause jobs to run immediately.
160 To unset a variable, set it to the empty string:
162 whenjobs --set var ""
164 By default variables are strings. You can also set the type of a
165 variable when setting it by adding the optional I<--type> parameter:
167 whenjobs --set free_space 10000 --type int
169 See the discussion of variable types in the L</REFERENCE> section
172 =item B<--start> "job name"
174 Start the job immediately and unconditionally.
176 This runs the job even if its normal preconditions are not met. This
177 may cause unexpected results, so use with caution.
181 Compile the jobs script and upload it to the daemon, without editing.
182 Note that the I<--edit> option does this automatically. Furthermore,
183 when the daemon is started it checks for a jobs script and loads it if
188 Display all the variables and their values, in the format C<name=value>.
194 Display the name and version of the program and exit.
200 Display brief usage and exit.
206 A whenjobs file consists of a series of one or more "every" or "when"
209 Comments in the file can be written using C<(* ... *)>. Comments
212 Shell script fragments are written using C<E<lt>E<lt> ... E<gt>E<gt>>.
213 Within shell script fragments, use C<#> for comments (as in ordinary
214 shell scripts). Because C<E<gt>E<gt>> has a special meaning, it
215 cannot be used in the shell script (ie. for redirection). You have to
216 write C<E<gt>\E<gt>> instead which is replaced with C<E<gt>E<gt>> when
217 the shell script is parsed.
219 =head2 EVERY STATEMENTS (PERIODIC JOBS)
221 An every statement has the form:
228 where C<E<lt>periodE<gt>> is a I<period expression>, which may take
229 one of the forms below. Don't forget the colon character between the
230 period expression and the shell script.
232 An every statement is a job which runs periodically.
234 =head3 PERIOD EXPRESSIONS
238 =item B<every second>
240 The job runs every second.
242 =item B<every minute>
244 The job runs every minute.
248 The job runs every hour.
252 The job runs every day, at midnight UTC.
256 The job runs every week, on a Thursday at midnight UTC.
260 The job runs every month, on the first of the month at midnight UTC.
264 The job runs every year, on the first day of the year at midnight UTC.
266 =item B<every decade>
268 =item B<every century>
270 =item B<every millenium>
272 The job runs every 10, 100 or 1000 years.
274 =item B<every I<N> seconds>
276 The job runs every I<N> seconds (I<N> is any number E<ge> 1).
278 =item B<every I<N> minutes>
280 The job runs every I<N> minutes.
282 =item B<every I<N> hours>
284 The job runs every I<N> hours.
286 =item B<every I<N> days>
288 The job runs every I<N> days.
290 =item B<every I<N> weeks>
292 The job runs every I<N> weeks.
294 =item B<every I<N> months>
296 The job runs every I<N> months.
298 =item B<every I<N> years>
300 =item B<every I<N> decades>
302 =item B<every I<N> centuries>
304 =item B<every I<N> millenia>
306 The job runs every I<N>, I<10*N>, I<100*N> or I<1000*N> years.
310 =head2 WHEN STATEMENTS (DEPENDENT JOBS)
312 A when statement has the form:
319 where C<E<lt>exprE<gt>> is a I<when expression>, described below.
320 Don't forget the colon character between the period expression and the
323 A when statement is a job which runs when the conditions described in
324 its when-expression become true.
326 When jobs are I<edge triggered>. This means that they run when the
327 condition changes from false to true (or in the case where the
328 expression has not been evaluated before, when it evaluates initially
331 =head3 WHEN EXPRESSIONS
333 When expressions are fully recursive expressions constructed from the
338 =item I<expr> B<&&> I<expr>
340 =item I<expr> B<||> I<expr>
342 The boolean "and" or "or" of the two sub-expressions.
344 =item I<expr> B<E<lt>> I<expr>
346 =item I<expr> B<E<lt>=> I<expr>
348 =item I<expr> B<==> I<expr>
350 =item I<expr> B<E<gt>=> I<expr>
352 =item I<expr> B<E<gt>> I<expr>
354 The two sub-expressions are evaluated and the usual comparison
355 operator is performed.
357 If the sub-expressions are numeric, then numeric comparison is done.
358 If either sub-expression is non-numeric, then both expressions are
359 converted (if necessary) to strings and string comparison is done.
363 Boolean negative of I<expr>.
365 =item I<expr> B<+> I<expr>
367 For numeric sub-expressions, this performs addition.
369 If both sub-expressions are strings, this performs string
372 Other types give an error.
374 =item I<expr> B<-> I<expr>
376 =item I<expr> B<*> I<expr>
378 =item I<expr> B</> I<expr>
380 =item I<expr> B<mod> I<expr>
382 Both sub-expressions are evaluated, and if both are numeric, then the
383 result is subtraction, multiplication, division or modulo.
385 Other types give an error. Note that I<mod> really is an infix
390 If I<expr> is a string, this returns the length of the string.
394 The value of the named variable.
396 Previously undefined variables are automatically initialized to the
399 =item B<prev> I<variable>
401 The I<previous> value of the named variable. This means, the value
402 that it had last time this when-job ran.
404 If the when-job has not run yet, then this returns C<"">.
406 Job state is preserved across file reloads, but I<only> for jobs that
407 are explicitly named. If you find that jobs using C<prev>, C<changes>
408 etc are running unnecessarily when the jobs file is edited or
409 uploaded, try giving the jobs an explicit name.
411 =item B<changes> I<variable>
413 If the named variable has changed since this job last ran, then this
414 evaluates to true, else false.
416 This is the same as writing C<prev variable == variable>.
418 =item B<increases> I<variable>
420 If the named variable has changed and increased since this job last
421 ran, then this evaluates to true, else false.
423 This is the same as writing C<prev variable E<lt> variable>.
425 =item B<decreases> I<variable>
427 If the named variable has changed and decreased since this job last
428 ran, then this evaluates to true, else false.
430 This is the same as writing C<prev variable E<gt> variable>.
432 B<Note:> There is a subtle gotcha with the I<decreases> operator: The
433 first time the expression is evaluated, the job has (by definition)
434 not yet run. Therefore C<prev variable> evaluates to C<""> (see
435 definition of I<prev> above). Since it is always true that
439 the I<decreases> operator evaluates to false, and since this usually
440 means the job does not run, the operator always evaluates to false.
442 To fix this, ensure that the variable is initialized (see
443 L</SETTING THE INITIAL VALUE OF VARIABLES> below).
447 This evaluates to true the first time the expression is evaluated
448 after the jobs file has been reloaded or the daemon restarted.
449 Thereafter it evaluates to false.
451 Don't use this to initialize variables: it won't do what you mean.
457 Constants that evaluate to boolean false or true respectively.
459 =item I<"any string">
463 In a boolean context, the empty string evaluates to false, and
464 non-empty strings evaluate to true.
468 Any integer. (Arbitrarily large integers are supported.)
470 In a boolean context, 0 evaluates to false, and non-zero evaluates to
481 Any floating point number.
483 In a boolean context, 0 evaluates to false, and non-zero evaluates to
490 The code between C<E<lt>E<lt> ... E<gt>E<gt>> is a shell script. It
491 is executed using C<$SHELL>, or if that environment variable is not
494 =head3 SHELL SCRIPT VARIABLES
496 Every variable that has been set (using the whenjobs I<--set> option)
497 is exported to the script, so you can simply get the value of any
498 variable by writing C<$name>.
500 In addition, there are some special variables available:
506 The name of the job. If the job has been named explicitly, then that
507 name is available through this variable, else it will be some implicit
512 The serial number of the job. This is simply a variable that
513 increments each time a job is run, and is unique to that run of the
518 Other environment variables such as C<$HOME>, C<$LOGNAME> etc are
521 =head3 SHELL SCRIPT TEMPORARY CURRENT DIRECTORY
523 The shell script runs with its current directory set to a temporary
524 directory. The temporary directory is removed when the shell script
525 exits. Therefore you can write temporary files here without worrying
526 about cleaning them up.
528 If you want to store permanent state, then you have to save it to a
529 well-known directory, eg. C<$HOME>, C</var> etc.
531 =head3 SHELL SCRIPT USER
533 The shell script runs as the ordinary user. It has no special
538 Jobs are given implicit names (C<job$1>, C<job$2> etc.). You can also
539 name jobs explicitly by preceeding the "every" or "when" statement
548 The job name is passed to the shell script in the C<$JOBNAME>
549 environment variable.
551 =head2 OCAML EXPRESSIONS
553 As well as simple "every" and "when" expressions, advanced users may
554 want to use arbitrary OCaml expressions, functions, etc in the jobs
555 script. These are useful for factoring common code or strings, for
556 setting the initial values of variables, or for defining pre and post
559 A simple example of an OCaml expression is:
561 let prefix = "daily_"
563 job (prefix ^ "virus_scan")
569 job (prefix ^ "disk_check")
575 which creates two jobs called C<"daily_virus_scan"> and
576 C<"daily_disk_check"> (C<^> is the OCaml string concatenation
579 OCaml expressions have access to a library of functions called
580 B<Whentools> which is described below. It lets you set variables,
581 create jobs algorithmically, etc.
583 The OCaml expressions run once, when the jobs file is being loaded or
586 =head3 SETTING THE INITIAL VALUE OF VARIABLES
588 Variables are created when they are referenced, and until set they
589 have the value empty string (just like the shell). Across file
590 reloads, the previous values of variables are preserved.
592 To initialize a variable to a known value when the jobs file is
593 loaded, call one of the C<Whentools.set_variable*> functions as in
597 Whentools.set_variable "name" "Richard";
598 Whentools.set_variable_int "counter" 0
602 Before a job runs, you can arrange that a C<pre> function is called.
603 This function may decide not to run the job (by returning C<false>).
605 One use for this is to prevent a particular job from running if there
606 is already an instance of the same job running:
609 pre (Whentools.one ())
612 # Takes longer than 10 seconds to run, but 'Whentools.one ()'
613 # will ensure only one is ever running.
617 When using pre functions, jobs must be given an explicit name, ie.
618 you must use the C<job> statement.
620 A number of pre functions are available in the library; see below.
622 You can also write your own post functions (in OCaml). The function
623 is passed one argument which is a C<Whentools.preinfo> struct, defined
624 below. It should return a boolean: C<true> if the job should run, and
625 C<false> if the job should not run.
627 Note that a fresh serial number (see L</JOBSERIAL>) is assigned to
628 each run, whether or not the job actually runs because of
631 =head3 POST FUNCTIONS
633 After a job runs, you can control what happens to its output by
634 writing a C<post> function. To write a post function you have to
635 name the job (ie. have an explicit C<job> statement). Put C<post ...>
636 after the job name like this:
639 post (Whentools.mailto "you@example.com")
645 A number of post functions are available in the library; see below.
647 You can also write your own post functions (in OCaml). The
648 function is passed one argument which is a C<Whentools.result> struct,
651 =head3 WHENTOOLS LIBRARY
657 =item B<Whentools.mailto> [I<~only_on_failure:true>]
658 [I<~from:from_address>] I<email_address> I<result>
660 This built-in post function sends the result of the script by email to
661 the given email address.
663 If the optional C<~only_on_failure:true> flag is set, then it is only
664 sent out if the script failed.
666 If the optional C<~from> flag is set, then the from address is set
667 accordingly. This is sometimes needed when sending mail.
669 Note the C<result> parameter is passed implicitly by the daemon. You
670 do not need to add it.
672 Here are some examples of using the mailto function:
675 post (Whentools.mailto "you@example.com")
682 post (Whentools.mailto ~only_on_failure:true
689 let from = "me@example.com"
690 let to_addr = "you@example.com"
693 post (Whentools.mailto ~from to_addr)
699 =item B<Whentools.max> I<n>
701 This built-in pre function ensures that a maximum of I<n> instances of
704 It checks the list of running jobs, and if I<n> or more instances are
705 already running, then it returns C<false>, which ensures that the new
708 =item B<Whentools.one> I<()>
710 This built-in pre function ensures that only one instance of the job
711 is running. It is the same as calling:
715 =item B<Whentools.set_variable> I<name> I<string>
717 Set variable I<name> to the string.
719 =item B<Whentools.set_variable_bool> I<name> I<b>
721 Set variable I<name> to the boolean value I<b>.
723 =item B<Whentools.set_variable_int> I<name> I<i>
725 Set variable I<name> to the integer value I<i>.
727 =item B<Whentools.set_variable_string> I<name> I<s>
729 Set variable I<name> to the string value <s>. This is
730 the same as I<Whentools.set_variable>.
732 =item B<Whentools.set_variable_float> I<name> I<f>
734 Set variable I<name> to the floating point value I<f>.
742 =item B<Whentools.preinfo>
744 This structure is passed to pre functions. It has the following
748 pi_job_name : string; # Job name.
749 pi_serial : Big_int.big_int; # Job serial number.
750 pi_variables : (string * variable) list; # Variables set in job.
751 pi_running : preinfo_running_job list; # List of running jobs.
753 and preinfo_running_job = {
754 pirun_job_name : string; # Running job name.
755 pirun_serial : Big_int.big_int; # Running job serial number.
756 pirun_start_time : float; # Running job start time.
757 pirun_pid : int; # Running job process ID.
760 =item B<Whentools.result>
762 This structure is passed to post functions. It has the following
766 res_job_name : string; # job name
767 res_serial : big_int; # job serial (same as $JOBSERIAL)
768 res_code : int; # return code from the shell script
769 res_tmpdir : string; # temporary directory script ran in
770 res_output : string; # filename of stdout/stderr output
771 res_start_time : float; # when the job started
780 =head1 ENVIRONMENT VARIABLES
790 Richard W.M. Jones L<http://people.redhat.com/~rjones/>
794 Copyright (C) 2012 Red Hat Inc.
796 This program is free software; you can redistribute it and/or modify
797 it under the terms of the GNU General Public License as published by
798 the Free Software Foundation; either version 2 of the License, or
799 (at your option) any later version.
801 This program is distributed in the hope that it will be useful,
802 but WITHOUT ANY WARRANTY; without even the implied warranty of
803 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
804 GNU General Public License for more details.
806 You should have received a copy of the GNU General Public License
807 along with this program; if not, write to the Free Software
808 Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.