Skip to content

Instantly share code, notes, and snippets.

@georgepsarakis
Created November 28, 2013 09:39
Show Gist options
  • Save georgepsarakis/7689438 to your computer and use it in GitHub Desktop.
Save georgepsarakis/7689438 to your computer and use it in GitHub Desktop.
Crontab file syntax check
#!/usr/bin/python
import argparse
import os
import re
'''
**** Crontab file syntax checking ***
Useful for scripts stored under /etc/cron.d
and for automating crontab task distribution
'''
class FileNotFoundException(Exception): pass
class ParseErrorException(Exception): pass
class UnknownPredefinedKeywordException(Exception): pass
class UnknownTimeEntryException(Exception): pass
class NoUserFoundException(Exception): pass
class InvalidStateException(Exception): pass
PREDEFINED = [ 'yearly', 'annually', 'monthly',
'weekly', 'daily', 'hourly', 'reboot' ]
R_comment = re.compile(r'^#')
R_predefined_keywords = re.compile(r'^@')
R_interval = re.compile(r'\*\/[0-9]+')
R_once_every = re.compile(r'[0-9]+')
R_always = re.compile(r'\*')
R_user = re.compile(r'^[a-z0-9_]+')
def is_none(v):
return v is None
def read_file(parameters):
if not os.path.exists(parameters.file):
raise Exception('File not found')
cron_file = []
with open(parameters.file, 'r') as f:
cron_file = [ line.strip() for line in f.readlines() ]
return cron_file
def check_time_entry(entry):
if not is_none(R_always.match(entry)):
return 'always'
elif not is_none(R_once_every.match(entry)):
return 'once'
elif not is_none(R_interval.match(entry)):
return 'interval'
elif not is_none(R_user.match(entry)):
return 'user'
return None
def parse_file(parameters):
cron_file = read_file(parameters)
with_user = parameters.user
for linenum, line in enumerate(cron_file):
linenum += 1
''' skip commented and empty lines '''
if line.strip() == "" or not is_none(R_comment.match(line)):
continue
if not is_none(R_predefined_keywords.match(line)):
if not line.split()[0].strip('@') in PREDEFINED:
raise UnknownPredefinedKeywordException('Line %d' % linenum)
return False
if with_user:
parameters = 6
else:
parameters = 5
time_entries = []
for status in [ check_time_entry(entry) for entry in line.split()[:parameters] ]:
if is_none(status):
raise UnknownTimeEntryException('Line %d' % linenum)
time_entries.append(status)
for position, state in enumerate(time_entries[:-1]):
if not state in [ "once", "always", "interval" ]:
raise InvalidStateException('Line %d, Entry %d' % (linenum, position + 1))
if with_user:
if time_entries[-1] != 'user':
raise NoUserFoundException('Line %d' % linenum)
return True
if __name__ == "__main__":
argparser = argparse.ArgumentParser('Cron file syntax check')
argparser.add_argument('-f', '--file', help='File to check.', required=True)
argparser.add_argument('-u', '--user', help='Add user in syntax (for scripts in /etc/cron.d).', required=False, default=False, action='store_true')
parameters = argparser.parse_args()
if parse_file(parameters):
print 'ok'
else:
print 'error'
@hema1024
Copy link

Hi, nice script .... but I tested the script with the following file, and it didnt throw an error (the first line is wrong it is missing one *

crontab for process2

05 * * * process2-staging --date=date +"\%Y\%m" --date="1 sec ago"
06 * * * * process2-staging --date=date +"\%Y\%m\%d" --date="1 sec ago"

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment