Simple static scheduler, with support for missed scheduled jobs using
statx(2)
. guile/scheme version of
sched.gpl, simplified and less
configurable.
There’s no need to manually compile this project, as it is Scheme code, which is
AOT-compiled by guile
. The Makefile
does provide some targets:
install
: install system-widetest
: runs the test suite
and some variables:
PREFIX
: install prefix (/usr/local/
)INFODIR
: info prefix, relative toPREFIX
(${PREFIX}/share/info/dir/
)DOCS
:y
orn
for compiling and installing documentation (y
)
Again, you can use the script as is, without needing to install it. It’s recomended you run the test suite, however.
You can pinpoint errors by running make tests
or make failed-tests
.
Run schedl [options] <jobfile>
.
Where <jobfile>
is a required job
file.
See schedl --help
.
jobfiles are scheme files that contain jobs to run on a time scope. A time scope should be any combination of Day-Month-Year and/or Day-Month scopes.
Each job expression is defined with the job
macro of the following form:
(job <time-scope> <jobs>)
Time scopes are scheme lists that serve as the first argument for a job expression. The main time scopes are of the form:
You can define a job to run given a month and a day using the syntax (<day>
<month>)
. For example:
(14 3)
: March 14th(9 10)
: October 9th(1 1)
: January 1st
You can substitute the day
field by a week
field. Those are sun
, mon
,
tue
, wed
, thu
, fri
and sat
. For example:
(sat 1)
: Every Saturday of January(mon 5)
: Every Monday of May(sun 12)
: Every Sunday of December
You can substitute a numeric month with a verbose month, with a three letter abbreviation. For example:
(sat jan)
: Every Saturday of January(mon may)
: Every Monday of May(sun dec)
: Every Sunday of December
You can define a job to run given an year, a month and a day using the syntax
(<day> <month> <year>)
. For example:
(6 5 2023)
: May 6th of 2023(1 1 1970)
: January 1st of 1970(19 1 2038)
: January 19th of 2038
In place of a literal field, you can use a matching expression that will match the certain element given some expression boundaries.
A *
token matches any value of the field it’s in. For example:
(* 3)
: Every day of March(14 *)
: Every 14th day of every month(* *)
: Every day of every month
A 1-nested list makes the job match in any of its predicates. It has the syntax
((<expression> ...))
, where <expression>
is a element of a job expression.
For example:
((25 31) dec)
: Every December 25th or 31st(1 (jan dec))
: Every 1st of January or 1st of December((1 10) (mar aug))
: Every 1st of March, 1st of August, 10th of March or 10th of August
A -
expression matches a value of a field whose LHS and RHS are the lower and
upper bounds respectively. It has the syntax (- <lower-bound> <upper-bound>
<step>?)
. For example:
(* (- jan mar))
: Every day of January, February or March((- 1 20) aug)
: Every day of August from 1 to 20((- 10 20) (- jan aug))
: Every day from January to August, from 10 to 20
A third field indicates a step. For example:
((- * * 3) 1)
: Every three days in January, starting from the 1st day((- 10 * 2) *)
: Every two days in every month, starting from the 10th day
You can omit fields with *
. Range fields can also be elements of
match-any-predicate
, for example:
((1 (- 10 20)) jan)
: Every 1st of January, or 10th through 20th of January
The maximum nesting of match-any-predicate
is 2. Meaning the example above
shows the deepest you can nest matchers.
If you omit the LHS, it’s assumed to be the primary of your field:
- January in the month field
- day 1 in the numeric day field
- Sunday in the week day field
If LHS is omitted, it won’t support step, unless RHS is also omitted.
If you omit the RHS, it’s assumed to be the last of your field:
- December in the month field
- day 31, 30 or 28 in the numeric day field (depending on the current matched month)
- Saturday in the week day field
The year field has no primaries nor lasts.
A job can be identified as the third argument of a job expression, after the time scope. Jobs are lists with job-scopes as elements.
For example:
(job (25 12) ((christmas "It's christmas")))
A job-scope
is of the form:
(<job-name> <job-description>)
where job-description
is a string. It can also have this form:
(<job-name> <job-description> . <job-execve>)
Where job-execve
is an execve(2)
compatible string. For example:
(job (* *) ((every-day "Script to run everyday" . "echo hello world")))
If a time-scope
matches a job, then the job name and description will be
written to STDOUT following the following format:
JOB: <job-name> <job-description>
or, in the case job-execve
is provided:
DO: <job-execve>
JOB: <job-name> <job-description>
For compatibility’s sake, it’s recomended to have it so that job-name
is a
valid file name in your file system.
You can use something like a shell script to add actions to these jobs (using
while read
and REGEXPS, for example). In the project repository root,
there’s a file called utils/schedl-handler-example.sh
, which you can follow
the comments and edit it to get an example handler.
There aren’t many options on schedl
. The only one that actually matters is
-k
: if schedl
is called with this option, then it won’t update the
jobfile
.
schedl
will check the mtime
of the file and take that as the last time the
file did its jobs. It will then see how many jobs happened between then and now.
If given the --dry
or -k
flag, it will not update the mtime
after
accessing the file. The default behavior is to update it.
In the project repository root, there’s a file called utils/edit-jobfile.sh
.
You can use it to edit the jobfile without changing the way schedl
would
interpret it. Invoke it like:
./utils/edit-jobfile.sh jobfile editor
where editor
is an optional argument, you can also pass it as the environment
varibale EDITOR
.
NOTE: If you want to write your own implementation for schedl
, feel free to
ignore this section.
In the case you want to use the default example as your frontend for schedl
,
here are some things you need to consider:
- job descriptions may not start with a
!
character:(job ... ((foo "foo bar")))
is fine,(job ... ((foo "!foo bar")))
isn’t
- job names may be valid file system file names:
(job ... ((foo-bar ...)))
is fine,(job ... ((foo/bar ...)))
isn’t
- job names may not end with
.sh
; that would make future handlers that look for the executable part of your job confused:(job ... ((foosh ...)))
is fine,(job ... ((foo.sh ...)))
isn’t
- job names may not end with
.log
; that would make future handlers that look for logs:(job ... ((foolog ...)))
is fine,(job ... ((foo.log ...)))
isn’t
The way this handler works (at least on my system), is that it gets called as
soon as the user logs in, then another script looks for files under
/tmp/schedl
, and it either prints it, or prompts for execution. The file
/tmp/schedl/session-lock
prevents schedl
from running again. It’s not
created in this script, it should be created on the one that handles the files
created by this script (which I didn’t added to this repository because it
doesn’t make sense to be here, but if you want to see an example, see:
https://github.com/matthmr/scripts/blob/master/x/xsession.)