1Python Crontab
2--------------
3
4.. image:: https://gitlab.com/doctormo/python-crontab/raw/master/branding.svg
5
6.. image:: https://badge.fury.io/py/python-crontab.svg
7 :target: https://badge.fury.io/py/python-crontab
8.. image:: https://img.shields.io/badge/License-LGPL%20v3-blue.svg
9 :target: https://gitlab.com/doctormo/python-crontab/raw/master/COPYING
10
11Bug Reports and Development
12===========================
13
14Please report any problems to the `GitLab issues tracker <https://gitlab.com/doctormo/python-crontab/issues>`_. Please use Git and push patches to the `GitLab project code hosting <https://gitlab.com/doctormo/python-crontab>`_.
15
16**Note:** If you get the error ``TypeError: __init__() takes exactly 2 arguments`` when using CronTab, you have the wrong module installed. You need to install ``python-crontab`` and not ``crontab`` from pypi or your local package manager and try again.
17
18Description
19===========
20
21Crontab module for reading and writing crontab files and accessing the system cron
22automatically and simply using a direct API.
23
24Comparing the `below chart <http://en.wikipedia.org/wiki/Cron#CRON_expression>`_
25you will note that W, L, # and ? symbols are not supported as they are not
26standard Linux or SystemV crontab format.
27
28+-------------+-----------+-----------------+-------------------+-------------+
29|Field Name |Mandatory |Allowed Values |Special Characters |Extra Values |
30+=============+===========+=================+===================+=============+
31|Minutes |Yes |0-59 |\* / , - | < > |
32+-------------+-----------+-----------------+-------------------+-------------+
33|Hours |Yes |0-23 |\* / , - | < > |
34+-------------+-----------+-----------------+-------------------+-------------+
35|Day of month |Yes |1-31 |\* / , - | < > |
36+-------------+-----------+-----------------+-------------------+-------------+
37|Month |Yes |1-12 or JAN-DEC |\* / , - | < > |
38+-------------+-----------+-----------------+-------------------+-------------+
39|Day of week |Yes |0-6 or SUN-SAT |\* / , - | < > |
40+-------------+-----------+-----------------+-------------------+-------------+
41
42Extra Values are '<' for minimum value, such as 0 for minutes or 1 for months.
43And '>' for maximum value, such as 23 for hours or 12 for months.
44
45Supported special cases allow crontab lines to not use fields.
46These are the supported aliases which are not available in SystemV mode:
47
48=========== ============
49Case Meaning
50=========== ============
51@reboot Every boot
52@hourly 0 * * * *
53@daily 0 0 * * *
54@weekly 0 0 * * 0
55@monthly 0 0 1 * *
56@yearly 0 0 1 1 *
57@annually 0 0 1 1 *
58@midnight 0 0 * * *
59=========== ============
60
61How to Use the Module
62=====================
63
64**Note:** Several users have reported their new crontabs not saving automatically or that the module doesn't do anything. You **MUST** use write() if you want your edits to be saved out. See below for full details on the use of the write function.
65
66Getting access to a crontab can happen in five ways, three system methods that
67will work only on Unix and require you to have the right permissions::
68
69 from crontab import CronTab
70
71 empty_cron = CronTab()
72 my_user_cron = CronTab(user=True)
73 users_cron = CronTab(user='username')
74
75And two ways from non-system sources that will work on Windows too::
76
77 file_cron = CronTab(tabfile='filename.tab')
78 mem_cron = CronTab(tab="""
79 * * * * * command
80 """)
81
82Special per-command user flag for vixie cron format (new in 1.9)::
83
84 system_cron = CronTab(tabfile='/etc/crontab', user=False)
85 job = system_cron[0]
86 job.user != None
87 system_cron.new(command='new_command', user='root')
88
89Creating a new job is as simple as::
90
91 job = cron.new(command='/usr/bin/echo')
92
93And setting the job's time restrictions::
94
95 job.minute.during(5,50).every(5)
96 job.hour.every(4)
97 job.day.on(4, 5, 6)
98
99 job.dow.on('SUN')
100 job.dow.on('SUN', 'FRI')
101 job.month.during('APR', 'NOV')
102
103Each time restriction will clear the previous restriction::
104
105 job.hour.every(10) # Set to * */10 * * *
106 job.hour.on(2) # Set to * 2 * * *
107
108Appending restrictions is explicit::
109
110 job.hour.every(10) # Set to * */10 * * *
111 job.hour.also.on(2) # Set to * 2,*/10 * * *
112
113Setting all time slices at once::
114
115 job.setall(2, 10, '2-4', '*/2', None)
116 job.setall('2 10 * * *')
117
118Setting the slice to a python date object::
119
120 job.setall(time(10, 2))
121 job.setall(date(2000, 4, 2))
122 job.setall(datetime(2000, 4, 2, 10, 2))
123
124Run a jobs command. Running the job here will not effect it's
125existing schedule with another crontab process::
126
127 job_standard_output = job.run()
128
129Creating a job with a comment::
130
131 job = cron.new(command='/foo/bar', comment='SomeID')
132
133Get the comment or command for a job::
134
135 command = job.command
136 comment = job.comment
137
138Modify the comment or command on a job::
139
140 job.set_command("new_script.sh")
141 job.set_comment("New ID or comment here")
142
143Disabled or Enable Job::
144
145 job.enable()
146 job.enable(False)
147 False is job.is_enabled()
148
149Validity Check::
150
151 True is job.is_valid()
152
153Use a special syntax::
154
155 job.every_reboot()
156
157Find an existing job by command sub-match or regular expression::
158
159 iter = cron.find_command('bar') # matches foobar1
160 iter = cron.find_command(re.compile(r'b[ab]r$'))
161
162Find an existing job by comment exact match or regular expression::
163
164 iter = cron.find_comment('ID or some text')
165 iter = cron.find_comment(re.compile(' or \w'))
166
167Find an existing job by schedule::
168
169 iter = cron.find_time(2, 10, '2-4', '*/2', None)
170 iter = cron.find_time("*/2 * * * *")
171
172Clean a job of all rules::
173
174 job.clear()
175
176Iterate through all jobs, this includes disabled (commented out) cron jobs::
177
178 for job in cron:
179 print(job)
180
181Iterate through all lines, this includes all comments and empty lines::
182
183 for line in cron.lines:
184 print(line)
185
186Remove Items::
187
188 cron.remove( job )
189 cron.remove_all('echo')
190 cron.remove_all(comment='foo')
191 cron.remove_all(time='*/2')
192
193Clear entire cron of all jobs::
194
195 cron.remove_all()
196
197Write CronTab back to system or filename::
198
199 cron.write()
200
201Write CronTab to new filename::
202
203 cron.write( 'output.tab' )
204
205Write to this user's crontab (unix only)::
206
207 cron.write_to_user( user=True )
208
209Write to some other user's crontab::
210
211 cron.write_to_user( user='bob' )
212
213Validate a cron time string::
214
215 from crontab import CronSlices
216 bool = CronSlices.is_valid('0/2 * * * *')
217
218Compare list of cron objects against another and return the difference::
219
220 difference = set([CronItem1, CronItem2, CronItem3]) - set([CronItem2, CronItem3])
221
222Compare two CronItems for equality::
223
224 CronItem1 = CronTab(tab="* * * * * COMMAND # Example Job")
225 CronItem2 = CronTab(tab="10 * * * * COMMAND # Example Job 2")
226 if CronItem1 != CronItem2:
227 print("Cronjobs do not match")
228
229Environment Variables
230=====================
231
232Some versions of vixie cron support variables outside of the command line.
233Sometimes just update the envronment when commands are run, the Cronie fork
234of vixie cron also supports CRON_TZ which looks like a regular variable but
235actually changes the times the jobs are run at.
236
237Very old vixie crons don't support per-job variables, but most do.
238
239Iterate through cron level environment variables::
240
241 for (name, value) in cron.env.items():
242 print(name)
243 print(value)
244
245Create new or update cron level environment variables::
246
247 print(cron.env['SHELL'])
248 cron.env['SHELL'] = '/bin/bash'
249 print(cron.env)
250
251Each job can also have a list of environment variables::
252
253 for job in cron:
254 job.env['NEW_VAR'] = 'A'
255 print(job.env)
256
257
258Proceeding Unit Confusion
259=========================
260
261It is sometimes logical to think that job.hour.every(2) will set all proceeding
262units to '0' and thus result in "0 \*/2 * * \*". Instead you are controlling
263only the hours units and the minute column is unaffected. The real result would
264be "\* \*/2 * * \*" and maybe unexpected to those unfamiliar with crontabs.
265
266There is a special 'every' method on a job to clear the job's existing schedule
267and replace it with a simple single unit::
268
269 job.every(4).hours() == '0 */4 * * *'
270 job.every().dom() == '0 0 * * *'
271 job.every().month() == '0 0 0 * *'
272 job.every(2).dows() == '0 0 * * */2'
273
274This is a convenience method only, it does normal things with the existing api.
275
276Running the Scheduler
277=====================
278
279The module is able to run a cron tab as a daemon as long as the optional
280croniter module is installed; each process will block and errors will
281be logged (new in 2.0).
282
283(note this functionality is new and not perfect, if you find bugs report them!)
284
285Running the scheduler::
286
287 tab = CronTab(tabfile='MyScripts.tab')
288 for result in tab.run_scheduler():
289 print("This was printed to stdout by the process.")
290
291Do not do this, it won't work because it returns generator function::
292
293 tab.run_scheduler()
294
295Timeout and cadence can be changed for testing or error management::
296
297 for result in tab.run_scheduler(timeout=600):
298 print("Will run jobs every 1 minutes for ten minutes from now()")
299
300 for result in tab.run_scheduler(cadence=1, warp=True):
301 print("Will run jobs every 1 second, counting each second as 1 minute")
302
303Frequency Calculation
304=====================
305
306Every job's schedule has a frequency. We can attempt to calculate the number
307of times a job would execute in a give amount of time. We have three simple
308methods::
309
310 job.setall("1,2 1,2 * * *")
311 job.frequency_per_day() == 4
312
313The per year frequency method will tell you how many days a year the
314job would execute::
315
316 job.setall("* * 1,2 1,2 *")
317 job.frequency_per_year(year=2010) == 4
318
319These are combined to give the number of times a job will execute in any year::
320
321 job.setall("1,2 1,2 1,2 1,2 *")
322 job.frequency(year=2010) == 16
323
324Frequency can be quickly checked using python built-in operators::
325
326 job < "*/2 * * * *"
327 job > job2
328 job.slices == "*/5"
329
330Log Functionality
331=================
332
333The log functionality will read a cron log backwards to find you the last run
334instances of your crontab and cron jobs.
335
336The crontab will limit the returned entries to the user the crontab is for::
337
338 cron = CronTab(user='root')
339
340 for d in cron.log:
341 print(d['pid'] + " - " + d['date'])
342
343Each job can return a log iterator too, these are filtered so you can see when
344the last execution was::
345
346 for d in cron.find_command('echo')[0].log:
347 print(d['pid'] + " - " + d['date'])
348
349All System CronTabs Functionality
350=================================
351
352The crontabs (note the plural) module can attempt to find all crontabs on the
353system. This works well for Linux systems with known locations for cron files
354and user spolls. It will even extract anacron jobs so you can get a picture
355of all the jobs running on your system::
356
357 from crontabs import CronTabs
358
359 for cron in CronTabs():
360 print(repr(cron))
361
362All jobs can be brought together to run various searches, all jobs are added
363to a CronTab object which can be used as documented above::
364
365 jobs = CronTabs().all.find_command('foo')
366
367Schedule Functionality
368======================
369
370If you have the croniter python module installed, you will have access to a
371schedule on each job. For example if you want to know when a job will next run::
372
373 schedule = job.schedule(date_from=datetime.now())
374
375This creates a schedule croniter based on the job from the time specified. The
376default date_from is the current date/time if not specified. Next we can get
377the datetime of the next job::
378
379 datetime = schedule.get_next()
380
381Or the previous::
382
383 datetime = schedule.get_prev()
384
385The get methods work in the same way as the default croniter, except that they
386will return datetime objects by default instead of floats. If you want the
387original functionality, pass float into the method when calling::
388
389 datetime = schedule.get_current(float)
390
391If you don't have the croniter module installed, you'll get an ImportError when
392you first try using the schedule function on your cron job object.
393
394Descriptor Functionality
395========================
396
397If you have the cron-descriptor module installed, you will be able to ask for a
398translated string which describes the frequency of the job in the current
399locale language. This should be mostly human readable.
400
401
402 print(job.description(use_24hour_time_format=True))
403
404See cron-descriptor for details of the supported languages and options.
405
406Extra Support
407=============
408
409 - Support for vixie cron with username addition with user flag
410 - Support for SunOS, AIX & HP with compatibility 'SystemV' mode.
411 - Python 3 (3.5, 3.6, 3.7) and Python 2.7 tested, python 2.6 removed from support.
412 - Windows support works for non-system crontabs only.
413 ( see mem_cron and file_cron examples above for usage )
414