Shell Script – How to Add/Remove Cron Jobs by Script

cronshell-script

I'm going to make a bash script that is executed at boot and runs periodically.

I want it user-configurable, so that a user can add a cron job 0 * * * * my_script by running my_script add 0 * * * *, list jobs by my_script list, and remove by my_script remove job_number where the job number is listed in the output of my_script list command.

If I could manage crontab files separately, this would be easily achieved.
However, It seems crontab is only one file per a user (If not, please let me know). Directly dealing with that crontab file is a bad solution, of course.

So what is the proper way to handle the cron jobs? Or, is there a better way to handle periodically running scripts?

Conditions:

  1. Any user should be able to run it, whether privileged or not.
  2. No dependencies.

Additional question:

Since I couldn't find any proper way to manage periodically running scripts, I thought what I might be doing wrong. In the sense of software design, is it not practical to implement the interface to manage the software's scheduled tasks? Should I leave all schedule managements to users?

Best Answer

Using cron is the correct way to schedule periodic running of tasks on most Unix systems. Using a personal crontab is the most convenient way for a user to schedule their own tasks. System tasks may be scheduled by root (not using the script below!) in the system crontab, which usually has an ever so slightly different format (an extra field with a username).

Here's a simple script for you. Any user may use this to manage their own personal crontab.

  • It doesn't do any type of validation of its input except that it will complain if you give it too few arguments. It is therefore completely possible to add improperly formatted crontab entries.

  • The remove sub-command takes a line number and will remove what's on that line in the crontab, regardless of what that is. The number is passed, unsanitized, directly to sed.

  • The crontab entry, when you add one, has to be quoted. This affects how you must handle quotes inside the crontab entry itself.

Most of those things should be relatively easy for you to fix.

#!/bin/sh

usage () {
    cat <<USAGE_END
Usage:
    $0 add "job-spec"
    $0 list
    $0 remove "job-spec-lineno"
USAGE_END
}

if [ -z "$1" ]; then
    usage >&2
    exit 1
fi

case "$1" in
    add)
        if [ -z "$2" ]; then
            usage >&2
            exit 1
        fi

        tmpfile=$(mktemp)

        crontab -l >"$tmpfile"
        printf '%s\n' "$2" >>"$tmpfile"
        crontab "$tmpfile" && rm -f "$tmpfile"
        ;;
    list)
        crontab -l | cat -n
        ;;
    remove)
        if [ -z "$2" ]; then
            usage >&2
            exit 1
        fi

        tmpfile=$(mktemp)

        crontab -l | sed -e "$2d" >"$tmpfile"
        crontab "$tmpfile" && rm -f "$tmpfile"
        ;;
    *)
        usage >&2
        exit 1
esac

Example of use:

$ ./script
Usage:
    ./script add "job-spec"
    ./script list
    ./script remove "job-spec-lineno"

$ ./script list
     1  */15 * * * * /bin/date >>"$HOME"/.fetchmail.log
     2  @hourly /usr/bin/newsyslog -r -f "$HOME/.newsyslog.conf"
     3  @reboot /usr/local/bin/fetchmail

$ ./script add "0 15 * * * echo 'hello world!'"

$ ./script list
     1  */15 * * * * /bin/date >>"$HOME"/.fetchmail.log
     2  @hourly /usr/bin/newsyslog -r -f "$HOME/.newsyslog.conf"
     3  @reboot /usr/local/bin/fetchmail
     4  0 15 * * * echo 'hello world!'

$ ./script remove 4

$ ./script list
     1  */15 * * * * /bin/date >>"$HOME"/.fetchmail.log
     2  @hourly /usr/bin/newsyslog -r -f "$HOME/.newsyslog.conf"
     3  @reboot /usr/local/bin/fetchmail
Related Question