Advertisement
Guest User

a grave mistake

a guest
Nov 15th, 2019
166
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
text 37.91 KB | None | 0 0
  1. #!/bin/bash
  2. #mycrontab.bash
  3. #Written by Piotr Pliszka, Michael Lewis, and Henry Moore.
  4.  
  5. #This entire snippet is meant to create a crontab in case there isn't one already present, it does so by trying to get the contents of the current crontab, and if they contain "no crontab for", it means that `crontab -l` returned an error message.
  6. crontab -l 2>&1 >/dev/null | grep "no crontab for" &> /dev/null
  7. if [ $? == 0 ]
  8. then
  9. echo No crontab present, creating...
  10. echo "" | crontab -
  11. fi
  12.  
  13. #Sets the variable $NEWLINE to contain the newline character for pretty printing.
  14. printf -v NEWLINE '\n'
  15.  
  16. #The periodicity function parses the individual cron job and ends up setting several global variables (global because bash is a mess and I couldn't figure out a less messy way of passing multiple variables worth of data around in any other way)
  17. #The function takes one parameter, which is the raw string containing the job to be parsed.
  18. #The variables set are:
  19. #$PERIOD_RES - Contains a human readable description of the job's periodicity
  20. #$JOB_RES - Contains the actual command to be executed by the job
  21. #$MIN - Contains the unformatted minute periodicity of the job
  22. #$HOUR - Contains the unformatted hour periodicity of the job
  23. #$DAYMONTH - Contains the unformatted day of the month periodicity of the job
  24. #$MONTH - Contains the unformatted month periodicity of the job
  25. #$DAYWEEK - Contains the unformatted day of the week periodicity of the job
  26. #Notes:
  27. #* This function does parse the special '@xxx' periodicity keywords, but the rest of the program does not cooperate with them. Assignment's description was unclear on whether this part was supposed to be handled or not. It wasn't clear on many things in general.
  28. #* While this function does parse both 'A-B' and 'A,B,C' syntax for each part of the periodicity, it does not parse the 'A-B/C' or other mixed syntaxes. Assignment's description was unclear on whether they were supposed to be handled or not.
  29. periodicity ()
  30. {
  31. #$JOB contains the raw string containing the job.
  32. local JOB=$1
  33. #This block of ifs checks for the periodicity keywords by just grepping for them.
  34. if echo "$JOB" | grep "@reboot" &> /dev/null ;
  35. then
  36. PERIOD_RES="Runs once after reboot."
  37. return
  38. elif echo "$JOB" | grep "@yearly" &> /dev/null ;
  39. then
  40. PERIOD_RES="Runs once a year."
  41. return
  42. elif echo "$JOB" | grep "@annually" &> /dev/null ;
  43. then
  44. PERIOD_RES="Runs once a year."
  45. return
  46. elif echo "$JOB" | grep "@monthly" &> /dev/null ;
  47. then
  48. PERIOD_RES="Runs once a month."
  49. return
  50. elif echo "$JOB" | grep "@weekly" &> /dev/null ;
  51. then
  52. PERIOD_RES="Runs once a week."
  53. return
  54. elif echo "$JOB" | grep "@daily" &> /dev/null ;
  55. then
  56. PERIOD_RES="Runs once a day."
  57. return
  58. elif echo "$JOB" | grep "@hourly" &> /dev/null ;
  59. then
  60. PERIOD_RES="Runs once an hour."
  61. return
  62. fi
  63.  
  64. PERIOD_RES=""
  65. JOB_RES=""
  66.  
  67. #$IFS is a special variable that stands for Internal Field Separator, the character(s) that bash uses to split a single string into multiple words.
  68. #read -a splits the input to individual words using the IFS and makes them into an array.
  69. IFS=' ' read -a FIELDS <<< "$JOB"
  70.  
  71. #Since the special keywords were taken care of, we can be sure that the first five fields of the job contain the specific periodicities
  72. MIN="${FIELDS[0]}"
  73. HOUR="${FIELDS[1]}"
  74. DAYMONTH="${FIELDS[2]}"
  75. MONTH="${FIELDS[3]}"
  76. DAYWEEK="${FIELDS[4]}"
  77.  
  78. #God help me this part was a nightmare
  79.  
  80.  
  81. #This entire massive block and the ones following it tries to build a human readable string that expresses the same periodicity as the cronjob syntax. Key word being 'tries'.
  82.  
  83. #The comments in the $MIN block are applicable to all the ones following it as well with minor changes.
  84.  
  85. PERIOD_RES="${NEWLINE}MINUTES: ${NEWLINE}"
  86.  
  87. if [ "$MIN" = "*" ]
  88. then
  89. PERIOD_RES="${PERIOD_RES}* Runs every minute. ${NEWLINE}"
  90. else
  91. IFS=',' read -a MIN_RANGE <<< "$MIN"
  92. for MIN_ENTRY in "${MIN_RANGE[@]}"; do
  93. if echo "$MIN_ENTRY" | grep -E "\*\/" &> /dev/null ;
  94. then
  95. IFS='/' read -a MIN_RANGE2 <<< "$MIN_ENTRY"
  96. PERIOD_RES="${PERIOD_RES}* Runs every ${MIN_RANGE2[1]} minutes, starting at the hour.${NEWLINE}"
  97. elif echo "$MIN_ENTRY" | grep -E "^[0-9]+-[0-9]+$" &> /dev/null ;
  98. then
  99. IFS='-' read -a MIN_RANGE2 <<< "$MIN_ENTRY"
  100. PERIOD_RES="${PERIOD_RES}* Runs every minute from ${MIN_RANGE2[0]} after the hour to ${MIN_RANGE2[1]} after the hour.${NEWLINE}"
  101. elif echo "$MIN_ENTRY" | grep -E "^[0-9]+-[0-9]+\/[0-9]+$" &> /dev/null ;
  102. then
  103. IFS='-/' read -a MIN_RANGE2 <<< "$MIN_ENTRY"
  104. PERIOD_RES="${PERIOD_RES}* Runs every ${MIN_RANGE2[2]} minutes from ${MIN_RANGE2[0]} after the hour to ${MIN_RANGE2[1]} after the hour.${NEWLINE}"
  105. else
  106. PERIOD_RES="${PERIOD_RES}* Runs ${MIN_ENTRY} minutes after the hour.${NEWLINE}"
  107. fi
  108.  
  109. done
  110. fi
  111.  
  112. PERIOD_RES="${PERIOD_RES}HOURS: ${NEWLINE}"
  113.  
  114. if [ "$HOUR" = "*" ]
  115. then
  116. PERIOD_RES="${PERIOD_RES}* Runs every hour. ${NEWLINE}"
  117. else
  118. IFS=',' read -a HOUR_RANGE <<< "$HOUR"
  119. for HOUR_ENTRY in "${HOUR_RANGE[@]}"; do
  120. if echo "$HOUR_ENTRY" | grep -E "\*\/" &> /dev/null ;
  121. then
  122. IFS='/' read -a HOUR_RANGE2 <<< "$HOUR_ENTRY"
  123. PERIOD_RES="${PERIOD_RES}* Runs every ${HOUR_RANGE2[1]} hours, starting at midnight.${NEWLINE}"
  124. elif echo "$HOUR_ENTRY" | grep -E "^[0-9]+-[0-9]+$" &> /dev/null ;
  125. then
  126. IFS='-' read -a HOUR_RANGE2 <<< "$HOUR_ENTRY"
  127. PERIOD_RES="${PERIOD_RES}* Runs every hour from ${HOUR_RANGE2[0]} to ${HOUR_RANGE2[1]}.${NEWLINE}"
  128. elif echo "$HOUR_ENTRY" | grep -E "^[0-9]+-[0-9]+\/[0-9]+$" &> /dev/null ;
  129. then
  130. IFS='-/' read -a HOUR_RANGE2 <<< "$HOUR_ENTRY"
  131. PERIOD_RES="${PERIOD_RES}* Runs every ${HOUR_RANGE2[2]} hours from ${HOUR_RANGE2[0]} to ${HOUR_RANGE2[1]}.${NEWLINE}"
  132. else
  133. PERIOD_RES="${PERIOD_RES}* Runs on ${HOUR_ENTRY} hour.${NEWLINE}"
  134. fi
  135.  
  136. done
  137. fi
  138.  
  139. DAYWEEKNAMES=(Sunday Monday Tuesday Wednesday Thursday Friday Saturday Sunday)
  140.  
  141. PERIOD_RES="${PERIOD_RES}DAYS OF THE WEEK: ${NEWLINE}"
  142.  
  143. if [ "$DAYWEEK" = "*" ]
  144. then
  145. PERIOD_RES="${PERIOD_RES}* Runs every day of the week. ${NEWLINE}"
  146. else
  147. IFS=',' read -a DAYWEEK_RANGE <<< "$DAYWEEK"
  148. for DAYWEEK_ENTRY in "${DAYWEEK_RANGE[@]}"; do
  149. if echo "$DAYWEEK_ENTRY" | grep -E "\*\/" &> /dev/null ;
  150. then
  151. IFS='/' read -a DAYWEEK_RANGE2 <<< "$DAYWEEK_ENTRY"
  152. PERIOD_RES="${PERIOD_RES}* Runs every ${DAYWEEK_RANGE2[1]} days, starting at Sunday.${NEWLINE}"
  153. elif echo "$DAYWEEK_ENTRY" | grep -E "^[0-9]+-[0-9]+$" &> /dev/null ;
  154. then
  155. IFS='-' read -a DAYWEEK_RANGE2 <<< "$DAYWEEK_ENTRY"
  156. DAYWEEK_0="${DAYWEEKNAMES[${DAYWEEK_RANGE2[0]}]}"
  157. DAYWEEK_1="${DAYWEEKNAMES[${DAYWEEK_RANGE2[1]}]}"
  158. PERIOD_RES="${PERIOD_RES}* Runs every day from ${DAYWEEK_0} to ${DAYWEEK_1}.${NEWLINE}"
  159. elif echo "$DAYWEEK_ENTRY" | grep -E "^[0-9]+-[0-9]+\/[0-9]+$" &> /dev/null ;
  160. then
  161. IFS='-/' read -a DAYWEEK_RANGE2 <<< "$DAYWEEK_ENTRY"
  162. DAYWEEK_0="${DAYWEEKNAMES[${DAYWEEK_RANGE2[0]}]}"
  163. DAYWEEK_1="${DAYWEEKNAMES[${DAYWEEK_RANGE2[1]}]}"
  164. PERIOD_RES="${PERIOD_RES}* Runs every ${DAYWEEK_RANGE2[2]} days from ${DAYWEEK_0} to ${DAYWEEK_1}.${NEWLINE}"
  165. elif echo "$DAYWEEK_ENTRY" | grep -E "^[a-zA-Z]+$" &> /dev/null ;
  166. then
  167. for DAYWEEK_NAME in "${DAYWEEKNAMES[@]}"; do
  168. CROP_DWEEK=${DAYWEEK_NAME::3}
  169.  
  170. if [ "${DAYWEEK_ENTRY^^}" = "${CROP_DWEEK^^}" ]
  171. then
  172. PERIOD_RES="${PERIOD_RES}* Runs on ${DAYWEEK_NAME}.${NEWLINE}"
  173. fi
  174. done
  175. else
  176. DAYWEEK_0="${DAYWEEKNAMES[${DAYWEEK_ENTRY}]}"
  177. PERIOD_RES="${PERIOD_RES}* Runs on ${DAYWEEK_0}.${NEWLINE}"
  178. fi
  179.  
  180. done
  181. fi
  182.  
  183. PERIOD_RES="${PERIOD_RES}DAYS OF THE MONTH: ${NEWLINE}"
  184.  
  185. if [ "$DAYMONTH" = "*" ]
  186. then
  187. PERIOD_RES="${PERIOD_RES}* Runs every day of the month. ${NEWLINE}"
  188. else
  189. IFS=',' read -a DAYMONTH_RANGE <<< "$DAYMONTH"
  190. for DAYMONTH_ENTRY in "${DAYMONTH_RANGE[@]}"; do
  191. if echo "$DAYMONTH_ENTRY" | grep -E "\*\/" &> /dev/null ;
  192. then
  193. IFS='/' read -a DAYMONTH_RANGE2 <<< "$DAYMONTH_ENTRY"
  194. PERIOD_RES="${PERIOD_RES}* Runs every ${DAYMONTH_RANGE2[1]} days, starting at the first of the month.${NEWLINE}"
  195. elif echo "$DAYMONTH_ENTRY" | grep -E "^[0-9]+-[0-9]+$" &> /dev/null ;
  196. then
  197. IFS='-' read -a DAYMONTH_RANGE2 <<< "$DAYMONTH_ENTRY"
  198. PERIOD_RES="${PERIOD_RES}* Runs every day from ${DAYMONTH_RANGE2[0]} to ${DAYMONTH_RANGE2[1]} of the month.${NEWLINE}"
  199. elif echo "$DAYMONTH_ENTRY" | grep -E "^[0-9]+-[0-9]+\/[0-9]+$" &> /dev/null ;
  200. then
  201. IFS='-/' read -a DAYMONTH_RANGE2 <<< "$DAYMONTH_ENTRY"
  202. PERIOD_RES="${PERIOD_RES}* Runs every ${DAYMONTH_RANGE2[2]} days from ${DAYMONTH_RANGE2[0]} to ${DAYMONTH_RANGE2[1]} of the month.${NEWLINE}"
  203. else
  204. PERIOD_RES="${PERIOD_RES}* Runs on ${DAYMONTH_ENTRY} of the month.${NEWLINE}"
  205. fi
  206.  
  207. done
  208. fi
  209.  
  210. MONTHNAMES=(_ January February March April May June July August September October November December)
  211.  
  212. PERIOD_RES="${PERIOD_RES}MONTHS: ${NEWLINE}"
  213.  
  214. if [ "$MONTH" = "*" ]
  215. then
  216. PERIOD_RES="${PERIOD_RES}* Runs every month. ${NEWLINE}"
  217. else
  218. IFS=',' read -a MONTH_RANGE <<< "$MONTH"
  219. for MONTH_ENTRY in "${MONTH_RANGE[@]}"; do
  220. if echo "$MONTH_ENTRY" | grep -E "\*\/" &> /dev/null ;
  221. then
  222. IFS='/' read -a MONTH_RANGE2 <<< "$MONTH_ENTRY"
  223. PERIOD_RES="${PERIOD_RES}* Runs every ${MONTH_RANGE2[1]} months, starting on January.${NEWLINE}"
  224. elif echo "$MONTH_ENTRY" | grep -E "^[0-9]+-[0-9]+$" &> /dev/null ;
  225. then
  226. IFS='-' read -a MONTH_RANGE2 <<< "$MONTH_ENTRY"
  227. MONTH_0="${MONTHNAMES[${MONTH_RANGE2[0]}]}"
  228. MONTH_1="${MONTHNAMES[${MONTH_RANGE2[1]}]}"
  229. PERIOD_RES="${PERIOD_RES}* Runs every month from ${MONTH_0} to ${MONTH_1}.${NEWLINE}"
  230. elif echo "$MONTH_ENTRY" | grep -E "^[0-9]+-[0-9]+\/[0-9]+$" &> /dev/null ;
  231. then
  232. IFS='-/' read -a MONTH_RANGE2 <<< "$MONTH_ENTRY"
  233. MONTH_0="${MONTHNAMES[${MONTH_RANGE2[0]}]}"
  234. MONTH_1="${MONTHNAMES[${MONTH_RANGE2[1]}]}"
  235. PERIOD_RES="${PERIOD_RES}* Runs every ${MONTH_RANGE2[2]} months from ${MONTH_0} to ${MONTH_1}.${NEWLINE}"
  236. elif echo "$MONTH_ENTRY" | grep -E "^[a-zA-Z]+$" &> /dev/null ;
  237. then
  238. for MONTH_NAME in "${MONTHNAMES[@]}"; do
  239. CROP_MONTH=${MONTH_NAME::3}
  240. if [ "${MONTH_ENTRY^^}" = "${CROP_MONTH^^}" ]
  241. then
  242. PERIOD_RES="${PERIOD_RES}* Runs on ${MONTH_NAME}.${NEWLINE}"
  243. fi
  244. done
  245. else
  246. MONTH_0="${MONTHNAMES[${MONTH_ENTRY}]}"
  247. PERIOD_RES="${PERIOD_RES}* Runs on ${MONTH_0}.${NEWLINE}"
  248. fi
  249. done
  250. fi
  251.  
  252. #This loop just concatenates all the rest of the fields of the original job back into the command they were supposed to be.
  253. for ((I = 5; I < ${#FIELDS[@]}; ++I)); do
  254. JOB_RES="${JOB_RES}${FIELDS[$I]} "
  255. done
  256. }
  257.  
  258.  
  259.  
  260. #This function acts as a basis of the 'Display Crontab Jobs' command, with the secondary purpose of setting the $JOB_NUM global variable to the number of jobs present.
  261. #It takes in an optional argument as to whether to run 'muted' (not display any output, just for setting the $JOB_NUM variable)
  262. process_cron ()
  263. {
  264. local MUTE=$1
  265.  
  266. #We get the raw output of the `crontab -l` command this way.
  267. local CRONTAB=$(crontab -l)
  268. JOB_NUM=0
  269.  
  270. while read -r LINE; do
  271. #This syntax is awkward because it's bash and I couldn't figure out how to turn it into something that doesn't hurt to read. Then again if we were meant to write something that doesn't hurt to read we wouldn't be using bash in the first place.
  272. FIRST_CHAR=${LINE::1}
  273.  
  274. echo "$FIRST_CHAR" | grep "#" &> /dev/null ;
  275.  
  276. #As to what is actually going on- the special $? variable contains the return code (not output) of the last ran command, which in this case is the grep above that checks whether a given line contains a '#' character and is therefore a comment. In grep's case, return code of anything but 0 meant that it did find something.
  277. #This is then ANDed with a check that the line isn't empty, which by the process of exclusion means that the line is a crontab job.
  278. if [ $? != "0" ] && [ "$LINE" != "" ]
  279. then
  280. #$JOB_NUM is incremented before the job is displayed because 0 based indexing is unintuitive or something.
  281. JOB_NUM=$((JOB_NUM+1))
  282. #And that's it unless the function isn't 'muted', in which case it also does the job of displaying the crontab jobs.
  283. if [ "$MUTE" != "1" ]
  284. then
  285. periodicity "$LINE"
  286. echo "${JOB_NUM}.) ${PERIOD_RES}${NEWLINE}Command to run: ${JOB_RES}${NEWLINE}"
  287. fi
  288. fi
  289. done <<< "$CRONTAB"
  290.  
  291. #A fallback message in case there's nothing to list.
  292. if [ "${JOB_NUM}" = 0 ]
  293. then
  294. echo "No jobs to list."
  295. fi
  296. }
  297.  
  298. #Big brother of the function above, works similarly to start with before going deep into the editing code.
  299. #It uses the global $EDIT_JOB variable to determine which job is to be edited. In all other cases it just copies the jobs as they are, without preserving comments or empty lines. And again, the assignment was unclear on whether comments or empty lines were to be preserved when editing the crontab.
  300. edit_job ()
  301. {
  302. local CRONTAB=$(crontab -l)
  303. JOB_NUM=0
  304.  
  305. while read -r LINE; do
  306.  
  307. FIRST_CHAR=${LINE::1}
  308.  
  309. echo "$FIRST_CHAR" | grep "#" &> /dev/null ;
  310.  
  311.  
  312. if [ $? != "0" ] && [ "$LINE" != "" ]
  313. then
  314. JOB_NUM=$((JOB_NUM+1))
  315. if [ "$JOB_NUM" != "0" ]
  316. then
  317. EDIT_CRONTAB="${EDIT_CRONTAB}${NEWLINE}"
  318. fi
  319.  
  320. #When we get to the job to edit we go into the whole new fancy menu, mimicking the main menu of the application.
  321. if [ "$JOB_NUM" = "$EDIT_JOB" ]
  322. then
  323. periodicity "$LINE"
  324. echo "${JOB_NUM}.) ${PERIOD_RES}${NEWLINE}${JOB_RES}${NEWLINE}"
  325.  
  326. #9 is, as in the main menu, used as a 'finish'/'exit' option.
  327. while [ "$INPUT_EDIT" != 9 ] ; do
  328.  
  329. echo "--------------------------"
  330. echo "1. Edit Minute periodicity"
  331. echo "2. Edit Hour periodicity"
  332. echo "3. Edit Day of the month periodicity"
  333. echo "4. Edit Month periodicity"
  334. echo "5. Edit Day of the week periodicity"
  335. echo "6. Edit Job command periodicity"
  336. echo "9. Finish Edit"
  337. echo "--------------------------"
  338. read -p "Select > " INPUT_EDIT </dev/tty
  339.  
  340. #This whole parsing/reading code is copied from the `insert_job` function further down, which will contain the comments for them both.
  341. case $INPUT_EDIT in
  342. 1)
  343. local INSERT_MIN_OKAY=0
  344. while [ "$INSERT_MIN_OKAY" = "0" ] ; do
  345. read -p "${NEWLINE}Input the job's new minutes periodicity. Valid inputs are single numbers from 0 to 59 inclusive, comma-separated lists of numbers from 0 to 59, two numbers from 0 to 59 separated by a minus sign \"-\", or a star \"*\" meaning that the job is to be ran every minute. > " INSERT_MIN_INPUT </dev/tty
  346.  
  347. if [ "$INSERT_MIN_INPUT" = "*" ]
  348. then
  349. INSERT_MIN_OKAY=1
  350. elif echo "$INSERT_MIN_INPUT" | grep -E "^[0-5]?[0-9]-[0-5]?[0-9]$" &> /dev/null ;
  351. then
  352. INSERT_MIN_OKAY=1
  353. elif echo "$INSERT_MIN_INPUT" | grep -E "^[0-5]?[0-9](,[0-5]?[0-9])*$" &> /dev/null ;
  354. then
  355. INSERT_MIN_OKAY=1
  356. else
  357. INSERT_MIN_OKAY=0
  358. echo "Error: Minutes periodicity has been specified incorrectly.${NEWLINE}${NEWLINE}"
  359. fi
  360. done
  361. MIN="${INSERT_MIN_INPUT}"
  362. ;;
  363. 2)
  364. local INSERT_HOUR_OKAY=0
  365. while [ "$INSERT_HOUR_OKAY" = "0" ] ; do
  366. read -p "${NEWLINE}Input the job's new hours periodicity. Valid inputs are single numbers from 0 to 23 inclusive, comma-separated lists of numbers from 0 to 23, two numbers from 0 to 23 separated by a minus sign \"-\", or a star \"*\" meaning that the job is to be ran every hour. > " INSERT_HOUR_INPUT </dev/tty
  367.  
  368. if [ "$INSERT_HOUR_INPUT" = "*" ]
  369. then
  370. INSERT_HOUR_OKAY=1
  371. elif echo "$INSERT_HOUR_INPUT" | grep -E "^(1?[0-9]|2[0-3])-(1?[0-9]|2[0-3])$" &> /dev/null ;
  372. then
  373. INSERT_HOUR_OKAY=1
  374. elif echo "$INSERT_HOUR_INPUT" | grep -E "^(1?[0-9]|2[0-3])(,(1?[0-9]|2[0-3]))*$" &> /dev/null ;
  375. then
  376. INSERT_HOUR_OKAY=1
  377. else
  378. INSERT_HOUR_OKAY=0
  379. echo "Error: Hours periodicity has been specified incorrectly.${NEWLINE}${NEWLINE}"
  380. fi
  381. done
  382. HOUR="${INSERT_HOUR_INPUT}"
  383. ;;
  384. 3)
  385. local INSERT_DAYMONTH_OKAY=0
  386. while [ "$INSERT_DAYMONTH_OKAY" = "0" ] ; do
  387. read -p "${NEWLINE}Input the job's new days of the month periodicity. Valid inputs are single numbers from 1 to 31 inclusive, comma-separated lists of numbers from 1 to 31, two numbers from 1 to 31 separated by a minus sign \"-\", or a star \"*\" meaning that the job is to be ran every day. > " INSERT_DAYMONTH_INPUT </dev/tty
  388.  
  389. if [ "$INSERT_DAYMONTH_INPUT" = "*" ]
  390. then
  391. INSERT_DAYMONTH_OKAY=1
  392. elif echo "$INSERT_DAYMONTH_INPUT" | grep -E "^([1-2]?[1-9]|[1-2]0|3[0-1])-([1-2]?[1-9]|[1-2]0|3[0-1])$" &> /dev/null ;
  393. then
  394. INSERT_DAYMONTH_OKAY=1
  395. elif echo "$INSERT_DAYMONTH_INPUT" | grep -E "^([1-2]?[1-9]|[1-2]0|3[0-1])(,([1-2]?[1-9]|[1-2]0|3[0-1]))*$" &> /dev/null ;
  396. then
  397. INSERT_DAYMONTH_OKAY=1
  398. else
  399. INSERT_DAYMONTH_OKAY=0
  400. echo "Error: Day of the month periodicity has been specified incorrectly.${NEWLINE}${NEWLINE}"
  401. fi
  402. done
  403. DAYMONTH="${INSERT_DAYMONTH_INPUT}"
  404. ;;
  405. 4)
  406. local INSERT_MONTH_OKAY=0
  407. while [ "$INSERT_MONTH_OKAY" = "0" ] ; do
  408. read -p "${NEWLINE}Input the job's new months periodicity. Valid inputs are single numbers from 1 to 12 inclusive, comma-separated lists of numbers from 1 to 12, two numbers from 1 to 12 separated by a minus sign \"-\", or a star \"*\" meaning that the job is to be ran every month. > " INSERT_MONTH_INPUT </dev/tty
  409.  
  410. if [ "$INSERT_MONTH_INPUT" = "*" ]
  411. then
  412. INSERT_MONTH_OKAY=1
  413. elif echo "$INSERT_MONTH_INPUT" | grep -E "^([1-9]|1[0-2])-([1-9]|1[0-2])$" &> /dev/null ;
  414. then
  415. INSERT_MONTH_OKAY=1
  416. elif echo "$INSERT_MONTH_INPUT" | grep -E "^([1-9]|1[0-2])(,([1-9]|1[0-2]))*$" &> /dev/null ;
  417. then
  418. INSERT_MONTH_OKAY=1
  419. else
  420. INSERT_MONTH_OKAY=0
  421. echo "Error: Months periodicity has been specified incorrectly.${NEWLINE}${NEWLINE}"
  422. fi
  423. done
  424. MONTH="${INSERT_MONTH_INPUT}"
  425. ;;
  426. 5)
  427. local INSERT_DAYWEEK_OKAY=0
  428. while [ "$INSERT_DAYWEEK_OKAY" = "0" ] ; do
  429. read -p "${NEWLINE}Input the job's new days of the week periodicity. Valid inputs are single numbers from 0 to 7 inclusive, comma-separated lists of numbers from 0 to 7, two numbers from 0 to 7 separated by a minus sign \"-\", or a star \"*\" meaning that the job is to be ran every day. > " INSERT_DAYWEEK_INPUT </dev/tty
  430.  
  431. if [ "$INSERT_DAYWEEK_INPUT" = "*" ]
  432. then
  433. INSERT_DAYWEEK_OKAY=1
  434. elif echo "$INSERT_DAYWEEK_INPUT" | grep -E "^[0-7]-[0-7]$" &> /dev/null ;
  435. then
  436. INSERT_DAYWEEK_OKAY=1
  437. elif echo "$INSERT_DAYWEEK_INPUT" | grep -E "^[0-7](,[0-7])*$" &> /dev/null ;
  438. then
  439. INSERT_DAYWEEK_OKAY=1
  440. else
  441. INSERT_DAYWEEK_OKAY=0
  442. echo "Error: Days of the week periodicity has been specified incorrectly.${NEWLINE}${NEWLINE}"
  443. fi
  444. done
  445. DAYWEEK="${INSERT_DAYWEEK_INPUT}"
  446. ;;
  447. 6)
  448. #There's not a real way of arbitrarily parsing whether an inputted command is valid short of just running it, so we just assume the user knows what they're doing. They probably don't but it's fine.
  449. read -p "Input the new job's command. > " JOB_RES
  450. ;;
  451. 9)
  452. #The edited job replaces the original one in the new crontab.
  453. EDIT_CRONTAB="${EDIT_CRONTAB}${MIN} ${HOUR} ${DAYMONTH} ${MONTH} ${DAYWEEK} ${JOB_RES}"
  454. ;;
  455. *)
  456. echo "Invalid command, please try again."
  457. ;;
  458. esac
  459.  
  460. done
  461. else
  462. #In case the job isn't the one we're looking for, we just copy it verbatim.
  463. EDIT_CRONTAB="${EDIT_CRONTAB}${LINE}"
  464. fi
  465. fi
  466. #A neat syntax with while and read. I have absolutely no idea how or why it works, but I kinda doubt that our entire year combined has a combined beard long enough to even comprehend the actual explanation.
  467. done <<< "$CRONTAB"
  468.  
  469.  
  470. echo "${EDIT_CRONTAB}" > temp.file
  471. crontab temp.file
  472. rm temp.file
  473. }
  474.  
  475. #remove_job works very similarly to edit_job above, except it just doesn't copy the job specified by $REMOVE_JOB as opposed to editing it.
  476. remove_job ()
  477. {
  478. local CRONTAB=$(crontab -l)
  479. JOB_NUM=0
  480.  
  481. EDIT_CRONTAB=""
  482.  
  483. while read -r LINE; do
  484. FIRST_CHAR=${LINE::1}
  485.  
  486. echo "$FIRST_CHAR" | grep "#" &> /dev/null ;
  487.  
  488.  
  489. if [ $? != "0" ] && [ "$LINE" != "" ]
  490. then
  491. JOB_NUM=$((JOB_NUM+1))
  492.  
  493. #I don't know why I wrote it this way and I dare not try to remember
  494. if [ "${JOB_NUM}" != "1" ]
  495. then
  496. if [ "${JOB_NUM}" != "${REMOVE_JOB}" ]
  497. then
  498. EDIT_CRONTAB="${EDIT_CRONTAB}${NEWLINE}"
  499. fi
  500. fi
  501.  
  502. if [ "${JOB_NUM}" = "${REMOVE_JOB}" ]
  503. then :
  504. else
  505. EDIT_CRONTAB="${EDIT_CRONTAB}${LINE}"
  506. fi
  507.  
  508.  
  509. fi
  510. done <<< "$CRONTAB"
  511.  
  512. echo "${EDIT_CRONTAB}" > temp.file
  513. crontab temp.file
  514. rm temp.file
  515. }
  516.  
  517. #The big one with all the parsing code that is then borrowed by edit_job.
  518. insert_job ()
  519. {
  520. local INSERT_MIN_OKAY=0
  521. local INSERT_HOUR_OKAY=0
  522. local INSERT_DAYMONTH_OKAY=0
  523. local INSERT_DAYWEEK_OKAY=0
  524. local INSERT_MONTH_OKAY=0
  525.  
  526. #The outline of what this function does goes like this- Each part of cronjob's periodicity has its own while loop that keeps asking for proper input until it is provided, and then it moves to the next one, until all fields are provided.
  527. #Proper input is either a star character '*', the 'A-B' syntax or the 'A,B,C...' syntax, latter of which also takes care of when input is just a single number.
  528. #Of course, all the numbers (A,B,C...) have to be valid for the given field, which is handled by regexes and grep -E.
  529. #All requests to convert the regex soup into actual parsing code will be interpreted as requests for a duel to the death.
  530. while [ "$INSERT_MIN_OKAY" = "0" ] ; do
  531. read -p "${NEWLINE}Input new job's minutes periodicity. Valid inputs are single numbers from 0 to 59 inclusive, comma-separated lists of numbers from 0 to 59, two numbers from 0 to 59 separated by a minus sign \"-\", or a star \"*\" meaning that the job is to be ran every minute. > " INSERT_MIN_INPUT </dev/tty
  532.  
  533. if [ "$INSERT_MIN_INPUT" = "*" ]
  534. then
  535. INSERT_MIN_OKAY=1
  536. #As far as the regexes themselves go, all five blocks follow the same basic formula, where the first elif's regex is "^X-X$" and the second elif's regex is "^X(,X)*$", where X stands for the bit of regex that only matches valid numbers for a given block, so in case of minutes, numbers from 0 to 59.
  537. #In case of the MIN block, X is "[0-5]?[0-9]".
  538. #My regexes can probably all be improved, but thankfully they're not what is actually being graded.
  539. elif echo "$INSERT_MIN_INPUT" | grep -E "^[0-5]?[0-9]-[0-5]?[0-9]$" &> /dev/null ;
  540. then
  541. INSERT_MIN_OKAY=1
  542. elif echo "$INSERT_MIN_INPUT" | grep -E "^[0-5]?[0-9](,[0-5]?[0-9])*$" &> /dev/null ;
  543. then
  544. INSERT_MIN_OKAY=1
  545. else
  546. INSERT_MIN_OKAY=0
  547. echo "Error: Minutes periodicity has been specified incorrectly.${NEWLINE}${NEWLINE}"
  548. fi
  549. done
  550.  
  551. while [ "$INSERT_HOUR_OKAY" = "0" ] ; do
  552. read -p "${NEWLINE}Input new job's hours periodicity. Valid inputs are single numbers from 0 to 23 inclusive, comma-separated lists of numbers from 0 to 23, two numbers from 0 to 23 separated by a minus sign \"-\", or a star \"*\" meaning that the job is to be ran every hour. > " INSERT_HOUR_INPUT </dev/tty
  553.  
  554. if [ "$INSERT_HOUR_INPUT" = "*" ]
  555. then
  556. INSERT_HOUR_OKAY=1
  557. #In case of the HOUR block, X is "(1?[0-9]|2[0-3])".
  558. elif echo "$INSERT_HOUR_INPUT" | grep -E "^(1?[0-9]|2[0-3])-(1?[0-9]|2[0-3])$" &> /dev/null ;
  559. then
  560. INSERT_HOUR_OKAY=1
  561. elif echo "$INSERT_HOUR_INPUT" | grep -E "^(1?[0-9]|2[0-3])(,(1?[0-9]|2[0-3]))*$" &> /dev/null ;
  562. then
  563. INSERT_HOUR_OKAY=1
  564. else
  565. INSERT_HOUR_OKAY=0
  566. echo "Error: Hours periodicity has been specified incorrectly.${NEWLINE}${NEWLINE}"
  567. fi
  568. done
  569.  
  570. while [ "$INSERT_DAYMONTH_OKAY" = "0" ] ; do
  571. read -p "${NEWLINE}Input new job's days of the month periodicity. Valid inputs are single numbers from 1 to 31 inclusive, comma-separated lists of numbers from 1 to 31, two numbers from 1 to 31 separated by a minus sign \"-\", or a star \"*\" meaning that the job is to be ran every day. > " INSERT_DAYMONTH_INPUT </dev/tty
  572.  
  573. if [ "$INSERT_DAYMONTH_INPUT" = "*" ]
  574. then
  575. INSERT_DAYMONTH_OKAY=1
  576. #In case of the DAYMONTH block, X is "([1-2]?[1-9]|[1-2]0|3[0-1])"
  577. elif echo "$INSERT_DAYMONTH_INPUT" | grep -E "^([1-2]?[1-9]|[1-2]0|3[0-1])-([1-2]?[1-9]|[1-2]0|3[0-1])$" &> /dev/null ;
  578. then
  579. INSERT_DAYMONTH_OKAY=1
  580. elif echo "$INSERT_DAYMONTH_INPUT" | grep -E "^([1-2]?[1-9]|[1-2]0|3[0-1])(,([1-2]?[1-9]|[1-2]0|3[0-1]))*$" &> /dev/null ;
  581. then
  582. INSERT_DAYMONTH_OKAY=1
  583. else
  584. INSERT_DAYMONTH_OKAY=0
  585. echo "Error: Days of the month periodicity has been specified incorrectly.${NEWLINE}${NEWLINE}"
  586. fi
  587. done
  588.  
  589. while [ "$INSERT_DAYWEEK_OKAY" = "0" ] ; do
  590. read -p "${NEWLINE}Input new job's days of the week periodicity. Valid inputs are single numbers from 0 to 7 inclusive, comma-separated lists of numbers from 0 to 7, two numbers from 0 to 7 separated by a minus sign \"-\", or a star \"*\" meaning that the job is to be ran every day. > " INSERT_DAYWEEK_INPUT </dev/tty
  591.  
  592. if [ "$INSERT_DAYWEEK_INPUT" = "*" ]
  593. then
  594. INSERT_DAYWEEK_OKAY=1
  595. #In case of the DAYMONTH block, X is "[0-7]"
  596. elif echo "$INSERT_DAYWEEK_INPUT" | grep -E "^[0-7]-[0-7]$" &> /dev/null ;
  597. then
  598. INSERT_DAYWEEK_OKAY=1
  599. elif echo "$INSERT_DAYWEEK_INPUT" | grep -E "^[0-7](,[0-7])*$" &> /dev/null ;
  600. then
  601. INSERT_DAYWEEK_OKAY=1
  602. else
  603. INSERT_DAYWEEK_OKAY=0
  604. echo "Error: Days of the week periodicity has been specified incorrectly.${NEWLINE}${NEWLINE}"
  605. fi
  606. done
  607.  
  608. while [ "$INSERT_MONTH_OKAY" = "0" ] ; do
  609. read -p "${NEWLINE}Input new job's months periodicity. Valid inputs are single numbers from 1 to 12 inclusive, comma-separated lists of numbers from 1 to 12, two numbers from 1 to 12 separated by a minus sign \"-\", or a star \"*\" meaning that the job is to be ran every month. > " INSERT_MONTH_INPUT </dev/tty
  610.  
  611. if [ "$INSERT_MONTH_INPUT" = "*" ]
  612. then
  613. INSERT_MONTH_OKAY=1
  614. #In case of the DAYMONTH block, X is "([1-9]|1[0-2])"
  615. elif echo "$INSERT_MONTH_INPUT" | grep -E "^([1-9]|1[0-2])-([1-9]|1[0-2])$" &> /dev/null ;
  616. then
  617. INSERT_MONTH_OKAY=1
  618. elif echo "$INSERT_MONTH_INPUT" | grep -E "^([1-9]|1[0-2])(,([1-9]|1[0-2]))*$" &> /dev/null ;
  619. then
  620. INSERT_MONTH_OKAY=1
  621. else
  622. INSERT_MONTH_OKAY=0
  623. echo "Error: Months periodicity has been specified incorrectly.${NEWLINE}${NEWLINE}"
  624. fi
  625. done
  626.  
  627. #And then the actual command is not tested at all because Halting Problem
  628. read -p "Input the new job's command. > " INSERT_COMMAND
  629.  
  630. CRONTAB_DATA=$(crontab -l)
  631.  
  632.  
  633. #The new job is added by simple concatenation to the already existing crontab.
  634. CRONTAB_DATA="${CRONTAB_DATA}${NEWLINE}${INSERT_MIN_INPUT} ${INSERT_HOUR_INPUT} ${INSERT_DAYMONTH_INPUT} ${INSERT_MONTH_INPUT} ${INSERT_DAYWEEK_INPUT} ${INSERT_COMMAND}"
  635.  
  636.  
  637. echo "${CRONTAB_DATA}" > temp.file
  638. crontab temp.file
  639. rm temp.file
  640. }
  641.  
  642. #The main loop of the program itself. Behold its splendor.
  643. while [ "$INPUT" != "9" ]
  644. do
  645. echo "-----------------------"
  646. echo "1. Display crontab jobs"
  647. echo "2. Insert a job"
  648. echo "3. Edit a job"
  649. echo "4. Remove a job"
  650. echo "5. Remove all jobs"
  651. echo "9. Exit"
  652. echo "-----------------------"
  653. read -p "Select > " INPUT
  654.  
  655. case $INPUT in
  656. 1)
  657. #Lists all job with unmuted process_cron
  658. process_cron 0
  659. ;;
  660. 2)
  661. #Inserts a job with insert_job
  662. insert_job
  663. ;;
  664. 3)
  665. #First, gets the number of jobs with muted process_cron
  666. process_cron 1
  667. EDIT_INPUT_OKAY=0
  668. N_QUIT=0
  669.  
  670. #Then we make sure there are actual jobs to edit. If there aren't, we display an error message and move on.
  671. if [ "${JOB_NUM}" != "0" ]
  672. then
  673. #What follows is a simple loop that runs until the user inserts a valid job number to edit or the character 'n', which is hardcoded to cancel editing.
  674. while [ "${EDIT_INPUT_OKAY}" != "1" ] ; do
  675. read -p "${NEWLINE}Job to edit (Type 'n' to cancel editing) > " EDIT_JOB </dev/tty
  676. #This regex here just parses whether the provided string is a positive integer.
  677. if echo "${EDIT_JOB}" | grep -E "^[0-9]+$" &> /dev/null ;
  678. then
  679. #We test if the provided job number is actually in bounds of how many jobs exist.
  680. if [ "${EDIT_JOB}" -gt "0" ] && [ "${EDIT_JOB}" -le "${JOB_NUM}" ]
  681. then
  682. EDIT_INPUT_OKAY=1
  683. else
  684. EDIT_INPUT_OKAY=0
  685. #And here we remind them of how many jobs exist just in case.
  686. echo "Job number outside of bounds (1-${JOB_NUM}), please try again.${NEWLINE}"
  687. fi
  688. elif [ "${EDIT_JOB}" = "n" ]
  689. then
  690. EDIT_INPUT_OKAY=1
  691. N_QUIT=1
  692. else
  693. EDIT_INPUT_OKAY=0
  694. echo "Job number must be a number, please try again.${NEWLINE}"
  695. fi
  696. done
  697.  
  698. #$N_QUIT is a flag that specifies whether the editing was cancelled or not.
  699. if [ "${N_QUIT}" = "0" ]
  700. then
  701. edit_job
  702. fi
  703. else
  704. echo "Error: There are no jobs present to edit.${NEWLINE}"
  705. fi
  706.  
  707. ;;
  708. 4)
  709. #The following block works the exact same way as the one above. Just with removing instead of editing.
  710. process_cron 1
  711. EDIT_INPUT_OKAY=0
  712. N_QUIT=0
  713.  
  714. if [ "${JOB_NUM}" != "0" ]
  715. then
  716. while [ "${EDIT_INPUT_OKAY}" != "1" ] ; do
  717. read -p "${NEWLINE}Job to remove (Type 'n' to cancel removing) > " REMOVE_JOB </dev/tty
  718. if echo "${REMOVE_JOB}" | grep -E "^[0-9]+$" &> /dev/null ;
  719. then
  720. if [ "${REMOVE_JOB}" -gt "0" ] && [ "${REMOVE_JOB}" -le "${JOB_NUM}" ]
  721. then
  722. EDIT_INPUT_OKAY=1
  723. else
  724. EDIT_INPUT_OKAY=0
  725. echo "Job number outside of bounds (1-${JOB_NUM}), please try again.${NEWLINE}"
  726. fi
  727. elif [ "${REMOVE_JOB}" = "n" ]
  728. then
  729. EDIT_INPUT_OKAY=1
  730. N_QUIT=1
  731. else
  732. EDIT_INPUT_OKAY=0
  733. echo "Job number must be a number, please try again.${NEWLINE}"
  734. fi
  735. done
  736.  
  737. if [ "${N_QUIT}" = "0" ]
  738. then
  739. remove_job
  740. fi
  741. else
  742. echo "Error: There are no jobs present to remove.${NEWLINE}"
  743. fi
  744. ;;
  745. 5)
  746. #We ask the user very nicely in case they did a goof.
  747. read -p "${NEWLINE}Are you sure you want to remove all jobs? ('y' to accept, anything else to cancel) > " REMOVE_JOB_ALL </dev/tty
  748. if [ "${REMOVE_JOB_ALL}" = "y" ] || [ "${REMOVE_JOB_ALL}" = "Y" ]
  749. then
  750. echo "" | crontab -
  751. fi
  752. ;;
  753. 7)
  754. #Debug command that just prints the contents of the crontab file.
  755. printf "$(crontab -l)\n"
  756. ;;
  757. 9)
  758. ;;
  759. *)
  760. echo "Invalid command, please try again."
  761. ;;
  762. esac
  763. done
  764. #And here it is.
  765. #I'm freed at last
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement