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<--start> "job name"
154 Start the job immediately and unconditionally.
156 This runs the job even if its normal preconditions are not met. This
157 may cause unexpected results, so use with caution.
159 =item B<--set> variable value
161 =item B<--type> bool|int|float|string|unit
163 I<--set> sets the variable named C<variable> to the new C<value>. The
164 variable is created if it does not already exist. Note that setting a
165 variable can cause jobs to run immediately.
167 To unset a variable, set it to the empty string:
169 whenjobs --set var ""
171 By default variables are strings. You can also set the type of a
172 variable when setting it by adding the optional I<--type> parameter:
174 whenjobs --set free_space 10000 --type int
176 See the discussion of variable types in the L</REFERENCE> section
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 =item B<changes> I<variable>
408 If the named variable has changed since this job last ran, then this
409 evaluates to true, else false.
411 This is the same as writing C<prev variable == variable>.
413 =item B<increases> I<variable>
415 If the named variable has changed and increased since this job last
416 ran, then this evaluates to true, else false.
418 This is the same as writing C<prev variable E<lt> variable>.
420 =item B<decreases> I<variable>
422 If the named variable has changed and decreased since this job last
423 ran, then this evaluates to true, else false.
425 This is the same as writing C<prev variable E<gt> variable>.
427 B<Note:> There is a subtle gotcha with the I<decreases> operator: The
428 first time the expression is evaluated, the job has (by definition)
429 not yet run. Therefore C<prev variable> evaluates to C<""> (see
430 definition of I<prev> above). Since it is always true that
434 the I<decreases> operator evaluates to false, and since this usually
435 means the job does not run, the operator always evaluates to false.
437 To fix this, ensure that the variable is initialized (see
438 L</SETTING THE INITIAL VALUE OF VARIABLES> below).
442 This evaluates to true the first time the expression is evaluated
443 after the jobs file has been reloaded or the daemon restarted.
444 Thereafter it evaluates to false.
446 Don't use this to initialize variables: it won't do what you mean.
452 Constants that evaluate to boolean false or true respectively.
454 =item I<"any string">
458 In a boolean context, the empty string evaluates to false, and
459 non-empty strings evaluate to true.
463 Any integer. (Arbitrarily large integers are supported.)
465 In a boolean context, 0 evaluates to false, and non-zero evaluates to
476 Any floating point number.
478 In a boolean context, 0 evaluates to false, and non-zero evaluates to
485 The code between C<E<lt>E<lt> ... E<gt>E<gt>> is a shell script. It
486 is executed using C<$SHELL>, or if that environment variable is not
489 =head3 SHELL SCRIPT VARIABLES
491 Every variable that has been set (using the whenjobs I<--set> option)
492 is exported to the script, so you can simply get the value of any
493 variable by writing C<$name>.
495 In addition, there are some special variables available:
501 The name of the job. If the job has been named explicitly, then that
502 name is available through this variable, else it will be some implicit
507 The serial number of the job. This is simply a variable that
508 increments each time a job is run, and is unique to that run of the
513 Other environment variables such as C<$HOME>, C<$LOGNAME> etc are
516 =head3 SHELL SCRIPT TEMPORARY CURRENT DIRECTORY
518 The shell script runs with its current directory set to a temporary
519 directory. The temporary directory is removed when the shell script
520 exits. Therefore you can write temporary files here without worrying
521 about cleaning them up.
523 If you want to store permanent state, then you have to save it to a
524 well-known directory, eg. C<$HOME>, C</var> etc.
526 =head3 SHELL SCRIPT USER
528 The shell script runs as the ordinary user. It has no special
533 Jobs are given implicit names (C<job$1>, C<job$2> etc.). You can also
534 name jobs explicitly by preceeding the "every" or "when" statement
543 The job name is passed to the shell script in the C<$JOBNAME>
544 environment variable.
546 =head2 OCAML EXPRESSIONS
548 As well as simple "every" and "when" expressions, advanced users may
549 want to use arbitrary OCaml expressions, functions, etc in the jobs
550 script. These are useful for factoring common code or strings, for
551 setting the initial values of variables, or for defining pre and post
554 A simple example of an OCaml expression is:
556 let prefix = "daily_"
558 job (prefix ^ "virus_scan")
564 job (prefix ^ "disk_check")
570 which creates two jobs called C<"daily_virus_scan"> and
571 C<"daily_disk_check"> (C<^> is the OCaml string concatenation
574 OCaml expressions have access to a library of functions called
575 B<Whentools> which is described below. It lets you set variables,
576 create jobs algorithmically, etc.
578 The OCaml expressions run once, when the jobs file is being loaded or
581 =head3 SETTING THE INITIAL VALUE OF VARIABLES
583 Variables are created when they are referenced, and until set they
584 have the value empty string (just like the shell). Across file
585 reloads, the previous values of variables are preserved.
587 To initialize a variable to a known value when the jobs file is
588 loaded, call one of the C<Whentools.set_variable*> functions as in
592 Whentools.set_variable "name" "Richard";
593 Whentools.set_variable_int "counter" 0
597 Before a job runs, you can arrange that a C<pre> function is called.
598 This function may decide not to run the job (by returning C<false>).
600 One use for this is to prevent a particular job from running if there
601 is already an instance of the same job running:
604 pre (Whentools.one ())
607 # Takes longer than 10 seconds to run, but 'Whentools.one ()'
608 # will ensure only one is ever running.
612 When using pre functions, jobs must be given an explicit name, ie.
613 you must use the C<job> statement.
615 A number of pre functions are available in the library; see below.
617 You can also write your own post functions (in OCaml). The function
618 is passed one argument which is a C<Whentools.preinfo> struct, defined
619 below. It should return a boolean: C<true> if the job should run, and
620 C<false> if the job should not run.
622 Note that a fresh serial number (see L</JOBSERIAL>) is assigned to
623 each run, whether or not the job actually runs because of
626 =head3 POST FUNCTIONS
628 After a job runs, you can control what happens to its output by
629 writing a C<post> function. To write a post function you have to
630 name the job (ie. have an explicit C<job> statement). Put C<post ...>
631 after the job name like this:
634 post (Whentools.mailto "you@example.com")
640 A number of post functions are available in the library; see below.
642 You can also write your own post functions (in OCaml). The
643 function is passed one argument which is a C<Whentools.result> struct,
646 =head3 WHENTOOLS LIBRARY
652 =item B<Whentools.mailto> [I<~only_on_failure:true>]
653 [I<~from:from_address>] I<email_address> I<result>
655 This built-in post function sends the result of the script by email to
656 the given email address.
658 If the optional C<~only_on_failure:true> flag is set, then it is only
659 sent out if the script failed.
661 If the optional C<~from> flag is set, then the from address is set
662 accordingly. This is sometimes needed when sending mail.
664 Note the C<result> parameter is passed implicitly by the daemon. You
665 do not need to add it.
667 Here are some examples of using the mailto function:
670 post (Whentools.mailto "you@example.com")
677 post (Whentools.mailto ~only_on_failure:true
684 let from = "me@example.com"
685 let to_addr = "you@example.com"
688 post (Whentools.mailto ~from to_addr)
694 =item B<Whentools.max> I<n>
696 This built-in pre function ensures that a maximum of I<n> instances of
699 It checks the list of running jobs, and if I<n> or more instances are
700 already running, then it returns C<false>, which ensures that the new
703 =item B<Whentools.one> I<()>
705 This built-in pre function ensures that only one instance of the job
706 is running. It is the same as calling:
710 =item B<Whentools.set_variable> I<name> I<string>
712 Set variable I<name> to the string.
714 =item B<Whentools.set_variable_bool> I<name> I<b>
716 Set variable I<name> to the boolean value I<b>.
718 =item B<Whentools.set_variable_int> I<name> I<i>
720 Set variable I<name> to the integer value I<i>.
722 =item B<Whentools.set_variable_string> I<name> I<s>
724 Set variable I<name> to the string value <s>. This is
725 the same as I<Whentools.set_variable>.
727 =item B<Whentools.set_variable_float> I<name> I<f>
729 Set variable I<name> to the floating point value I<f>.
737 =item B<Whentools.preinfo>
739 This structure is passed to pre functions. It has the following
743 pi_job_name : string; # Job name.
744 pi_serial : Big_int.big_int; # Job serial number.
745 pi_variables : (string * variable) list; # Variables set in job.
746 pi_running : preinfo_running_job list; # List of running jobs.
748 and preinfo_running_job = {
749 pirun_job_name : string; # Running job name.
750 pirun_serial : Big_int.big_int; # Running job serial number.
751 pirun_start_time : float; # Running job start time.
752 pirun_pid : int; # Running job process ID.
755 =item B<Whentools.result>
757 This structure is passed to post functions. It has the following
761 res_job_name : string; # job name
762 res_serial : big_int; # job serial (same as $JOBSERIAL)
763 res_code : int; # return code from the shell script
764 res_tmpdir : string; # temporary directory script ran in
765 res_output : string; # filename of stdout/stderr output
766 res_start_time : float; # when the job started
775 =head1 ENVIRONMENT VARIABLES
785 Richard W.M. Jones L<http://people.redhat.com/~rjones/>
789 Copyright (C) 2012 Red Hat Inc.
791 This program is free software; you can redistribute it and/or modify
792 it under the terms of the GNU General Public License as published by
793 the Free Software Foundation; either version 2 of the License, or
794 (at your option) any later version.
796 This program is distributed in the hope that it will be useful,
797 but WITHOUT ANY WARRANTY; without even the implied warranty of
798 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
799 GNU General Public License for more details.
801 You should have received a copy of the GNU General Public License
802 along with this program; if not, write to the Free Software
803 Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.