#!/bin/bash # verifycron--Checks a crontab file to ensure that it's # formatted properly. Expects standard cron notation of # min hr dom mon dow CMD, # where min is 0-59, hr is 0-23, dom is 1-31, mon is 1-12 (or names) # and dow is 0-7 (or names). Fields can be ranges (a-e) or lists # separated by commas (a,c,z) or an asterisk. Note that the step # value notation of Vixie cron (e.g., 2-6/2) is not supported by # this script in its current version. proc validNum { # Return 0 if the number given is a valid integer and 1 if not. # Specify both number and maxvalue as args to the function. setglobal num = $1, max = $2 # Asterisk values in fields are rewritten as "X" for simplicity, # so any number in the form "X" is de facto valid. if test $num = "X" { return 0 } elif test ! -z $[echo $num | sed 's/[[:digit:]]//g] { # Stripped out all the digits, and the remainder isn't empty? No good. return 1 } elif test $num -gt $max { # Number is bigger than the maximum value allowed. return 1 } else { return 0 } } proc validDay { # Return 0 if the value passed to this function is a valid day name, # 1 otherwise. match $[echo $1 | tr '[:upper:]' '[:lower:]] { with sun*|mon*|tue*|wed*|thu*|fri*|sat* return 0 with X return 0 # Special case--it's a rewritten "*" with * return 1 } } proc validMon { # This function returns 0 if given a valid month name, 1 otherwise. match $[echo $1 | tr '[:upper:]' '[:lower:]] { with jan*|feb*|mar*|apr*|may|jun*|jul*|aug* return 0 with sep*|oct*|nov*|dec* return 0 with X return 0 # special case, it's an "*" with * return 1 } } proc fixvars { # Translate all '*' into 'X' to bypass shell expansion hassles. # Save original input as "sourceline" for error messages. setglobal sourceline = ""$min $hour $dom $mon $dow $command"" setglobal min = $[echo $min | tr '*' 'X] # minute setglobal hour = $[echo $hour | tr '*' 'X] # hour setglobal dom = $[echo $dom | tr '*' 'X] # day of month setglobal mon = $[echo $mon | tr '*' 'X] # month setglobal dow = $[echo $dow | tr '*' 'X] # day of week } if test $Argc -ne 1 || test ! -r $1 { # If no crontab filename is given or it's not readable by the script, fail. echo "Usage: $0 usercrontabfile" > !2; exit 1 } setglobal lines = '0', entries = '0', totalerrors = '0' # Go through the crontab file line by line, checking each one. while read min hour dom mon dow command { setglobal lines = "$shExpr(' $lines + 1 ')" setglobal errors = '0' if test -z $min -o $(min%${min#?}) = "#" { # If it's a blank line or the first character of the line is "#", skip it. continue # Nothing to check } sh-expr 'entries++' fixvars # At this point, all the fields in the current line are split out into # separate variables, with all asterisks replaced by "X" for convenience, # so let's check the validity of input fields... # Minute check for minslice in [$[echo $min | sed 's/[,-]/ /g]] { if ! validNum $minslice 60 { echo "Line $(lines): Invalid minute value \"$minslice\"" setglobal errors = '1' } } # Hour check for hrslice in [$[echo $hour | sed 's/[,-]/ /g]] { if ! validNum $hrslice 24 { echo "Line $(lines): Invalid hour value \"$hrslice\"" setglobal errors = '1' } } # Day of month check for domslice in [$[echo $dom | sed 's/[,-]/ /g]] { if ! validNum $domslice 31 { echo "Line $(lines): Invalid day of month value \"$domslice\"" setglobal errors = '1' } } # Month check: Has to check for numeric values and names both. # Remember that a conditional like "if ! cond" means that it's # testing whether the specified condition is FALSE, not true. for monslice in [$[echo $mon | sed 's/[,-]/ /g]] { if ! validNum $monslice 12 { if ! validMon $monslice { echo "Line $(lines): Invalid month value \"$monslice\"" setglobal errors = '1' } } } # Day of week check: Again, name or number is possible. for dowslice in [$[echo $dow | sed 's/[,-]/ /g]] { if ! validNum $dowslice 7 { if ! validDay $dowslice { echo "Line $(lines): Invalid day of week value \"$dowslice\"" setglobal errors = '1' } } } if test $errors -gt 0 { echo ">>>> $(lines): $sourceline" echo "" setglobal totalerrors = "$shExpr(' $totalerrors + 1 ')" } } < $1 # read the crontab passed as an argument to the script # Notice that it's here, at the very end of the "while" loop, that we # redirect the input so that the user-specified filename can be # examined by the script! echo "Done. Found $totalerrors errors in $entries crontab entries." exit 0