=encoding utf8 =head1 NAME whenjobs - A powerful but simple cron replacement =head1 SYNOPSIS Editing the jobs script: whenjobs -e | --edit whenjobs -l | --list Get and set variables: whenjobs --get variable whenjobs --set variable value [--type bool|int|float|string] whenjobs --variables Start and stop the per-user daemon: whenjobs --daemon-start whenjobs --daemon-stop whenjobs --daemon-status whenjobs --daemon-restart =head1 DESCRIPTION Whenjobs is a powerful but simple replacement for cron. It lets you run jobs periodically like cron, but it also lets you trigger jobs to run when user-defined variables are set or change value. Periodic jobs are written like this: every 10 minutes : << # Get the current load average. load=`awk '{print $1}' /proc/loadavg` whenjobs --set load $load --type float >> When-statements let you create jobs that run based on variables set elsewhere: when load >= 6 : << mail -s "ALERT: high load average: $load" $LOGNAME < /dev/null >> (When statements are "edge-triggered", meaning that this job will only run when the load goes from under 6 to E 6). Like L, whenjobs are controlled by a jobs file which can be edited from the command line: $ whenjobs -e Whenjobs uses a daemon called L. Unlike crond, this daemon runs as the same user. Each user who wants to use whenjobs starts their own daemon: $ whenjobs --daemon-start You can also have the daemon start as you when the machine boots by adding the following line to a boot file such as C. Replace C with your username: su username -c /usr/sbin/whenjobsd Variables are the key to expressing dependencies between whenjobs. Variables are stored (per-user) in the daemon. You can use the command line tool to examine and set variables: $ whenjobs --variables load=0.9 $ whenjobs --set cat sushi $ whenjobs --get cat sushi The act of setting a variable (using I<--set>) can trigger jobs to run. =head1 OPTIONS =over 4 =item B<--daemon-start> =item B<--daemon-stop> Start and stop the per-user daemon. =item B<--daemon-status> Prints the status of the daemon: C or C. =item B<--daemon-restart> Restart the daemon. (If it is not running, then this command starts it). =item B<-e> =item B<--edit> Edit the jobs script. If you make changes to the jobs script, then it is automatically uploaded to the daemon. The C<$EDITOR> environment variable is used for editing. If not set, C is used. =item B<--get> variable Print the value of a variable. =item B<-l> =item B<--list> List the jobs script. =item B<--lib> directory Set the library directory which needs to contain the auxiliary files C and C. Normally you do not need to specify this. However if you are running whenjobs without installing it, then you need to point this to the C directory from the source, eg: whenjobs --lib $builddir/lib -e =item B<--set> variable value =item B<--type> bool|int|float|string|unit I<--set> sets the variable named C to the new C. The variable is created if it does not already exist. Note that setting a variable can cause jobs to run immediately. To unset a variable, set it to the empty string: whenjobs --set var "" By default variables are strings. You can also set the type of a variable when setting it by adding the optional I<--type> parameter: whenjobs --set free_space 10000 --type int See the discussion of variable types in the L section below. =item B<--upload> Compile the jobs script and upload it to the daemon, without editing. Note that the I<--edit> option does this automatically. Furthermore, when the daemon is started it checks for a jobs script and loads it if found. =item B<--variables> Display all the variables and their values, in the format C. =item B<-V> =item B<--version> Display the name and version of the program and exit. =item B<-help> =item B<--help> Display brief usage and exit. =back =head1 REFERENCE A whenjobs file consists of a series of one or more "every" or "when" statements. Comments in the file can be written using C<(* ... *)>. Comments may be nested. Shell script fragments are written using CE ... EE>. Within shell script fragments, use C<#> for comments (as in ordinary shell scripts). Because CE> has a special meaning, it cannot be used in the shell script (ie. for redirection). You have to write C\E> instead which is replaced with CE> when the shell script is parsed. =head2 EVERY STATEMENTS (PERIODIC JOBS) An every statement has the form: every : << # shell script >> where CperiodE> is a I, which may take one of the forms below. Don't forget the colon character between the period expression and the shell script. An every statement is a job which runs periodically. =head3 PERIOD EXPRESSIONS =over 4 =item B The job runs every second. =item B The job runs every minute. =item B The job runs every hour. =item B The job runs every day, at midnight UTC. =item B The job runs every week, on a Thursday at midnight UTC. =item B The job runs every month, on the first of the month at midnight UTC. =item B The job runs every year, on the first day of the year at midnight UTC. =item B =item B =item B The job runs every 10, 100 or 1000 years. =item B seconds> The job runs every I seconds (I is any number E 1). =item B minutes> The job runs every I minutes. =item B hours> The job runs every I hours. =item B days> The job runs every I days. =item B weeks> The job runs every I weeks. =item B months> The job runs every I months. =item B years> =item B decades> =item B centuries> =item B millenia> The job runs every I, I<10*N>, I<100*N> or I<1000*N> years. =back =head2 WHEN STATEMENTS (DEPENDENT JOBS) A when statement has the form: when : << # shell script >> where CexprE> is a I, described below. Don't forget the colon character between the period expression and the shell script. A when statement is a job which runs when the conditions described in its when-expression become true. When jobs are I. This means that they run when the condition changes from false to true (or in the case where the expression has not been evaluated before, when it evaluates initially to true). =head3 WHEN EXPRESSIONS When expressions are fully recursive expressions constructed from the following elements: =over 4 =item I B<&&> I =item I B<||> I The boolean "and" or "or" of the two sub-expressions. =item I B> I =item I B=> I =item I B<==> I =item I B=> I =item I B> I The two sub-expressions are evaluated and the usual comparison operator is performed. If the sub-expressions are numeric, then numeric comparison is done. If either sub-expression is non-numeric, then both expressions are converted (if necessary) to strings and string comparison is done. =item B I Boolean negative of I. =item I B<+> I For numeric sub-expressions, this performs addition. If both sub-expressions are strings, this performs string concatenation. Other types give an error. =item I B<-> I =item I B<*> I =item I B I =item I B I Both sub-expressions are evaluated, and if both are numeric, then the result is subtraction, multiplication, division or modulo. Other types give an error. Note that I really is an infix operator. =item B I If I is a string, this returns the length of the string. =item I The value of the named variable. Previously undefined variables are automatically initialized to the empty string. =item B I The I value of the named variable. This means, the value that it had last time this when-job ran. If the when-job has not run yet, then this returns C<"">. =item B I If the named variable has changed since this job last ran, then this evaluates to true, else false. This is the same as writing C. =item B I If the named variable has changed and increased since this job last ran, then this evaluates to true, else false. This is the same as writing C variable>. =item B I If the named variable has changed and decreased since this job last ran, then this evaluates to true, else false. This is the same as writing C variable>. B There is a subtle and difficult problem with using the I operator: The first time the expression is evaluated, the job has (by definition) not yet run. Therefore C evaluates to C<""> (see definition of I above). Since C<"" E everything>, the I operator evaluates to false, and since this usually means the job does not run, the operator always evaluates to false. A future version of whenjobs will address this problem. =item B This evaluates to true the first time the expression is evaluated after the jobs file has been reloaded or the daemon restarted. Thereafter it evaluates to false. You can use this to initialize variables, but note that this does not solve the I operator problem described above, because variables are initialized too late to affect that. =item B =item B Constants that evaluate to boolean false or true respectively. =item I<"any string"> Any string. In a boolean context, the empty string evaluates to false, and non-empty strings evaluate to true. =item I Any integer. (Arbitrarily large integers are supported.) In a boolean context, 0 evaluates to false, and non-zero evaluates to true. =item I =item I<.N> =item I =item I Any floating point number. In a boolean context, 0 evaluates to false, and non-zero evaluates to true. =back =head2 SHELL SCRIPTS The code between CE ... EE> is a shell script. It is executed using C<$SHELL>, or if that environment variable is not set then C. =head3 SHELL SCRIPT VARIABLES Every variable that has been set (using the whenjobs I<--set> option) is exported to the script, so you can simply get the value of any variable by writing C<$name>. In addition, there are some special variables available: =over 4 =item C<$JOBNAME> The name of the job. If the job has been named explicitly, then that name is available through this variable, else it will be some implicit name like C. =item C<$JOBSERIAL> The serial number of the job. This is simply a variable that increments each time a job is run, and is unique to that run of the job. =back Other environment variables such as C<$HOME>, C<$LOGNAME> etc are available as normal. =head3 SHELL SCRIPT TEMPORARY CURRENT DIRECTORY The shell script runs with its current directory set to a temporary directory. The temporary directory is removed when the shell script exits. Therefore you can write temporary files here without worrying about cleaning them up. If you want to store permanent state, then you have to save it to a well-known directory, eg. C<$HOME>, C etc. =head3 SHELL SCRIPT USER The shell script runs as the ordinary user. It has no special privileges. =head2 JOB NAMES Jobs are given implicit names (C, C etc.). You can also name jobs explicitly by preceeding the "every" or "when" statement with C: job "poll source" every 10 seconds : << # ... >> The job name is passed to the shell script in the C<$JOBNAME> environment variable. =head2 OCAML EXPRESSIONS As well as simple "every" and "when" expressions, advanced users may want to use arbitrary OCaml expressions, functions, etc in the jobs script. These are useful for factoring common code or strings, for setting the initial values of variables, or for defining cleanup functions. A simple example of an OCaml expression is: let prefix = "daily_" job (prefix ^ "virus_scan") every day : << # ... >> job (prefix ^ "disk_check") every day : << # ... >> which creates two jobs called C<"daily_virus_scan"> and C<"daily_disk_check"> (C<^> is the OCaml string concatenation operator). The OCaml expressions run once, when the jobs file is being loaded or reloaded. =head1 FILES =head1 ENVIRONMENT VARIABLES =head1 SEE ALSO L =head1 AUTHOR Richard W.M. Jones L =head1 COPYRIGHT Copyright (C) 2012 Red Hat Inc. This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.