3

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?

4 Answers 4

9

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
6
  • Surely this would work, but I'm not asking how to add jobs at the last line and/or pick ones from all cron jobs and remove them. This may possibly collide with the other softwares (if they handle cron jobs poorly), and the users who deals with crontab frequently will end up with an ugly crontab file. I expected, for example, a folder per user which cron reads all the crontab files inside it and runs, so that I can make a separate crontab file for my script.
    – queuedq
    May 6, 2017 at 16:18
  • A nice example: GNOME's startup application works this way! It reads all scripts from ~/.config/autostart/. However, I found that there are so many things from Linux that share this kind of "philosophy" of crontab. I'm so confused now. Thanks for the script anyway. Maybe I'm going to use it.
    – queuedq
    May 6, 2017 at 16:20
  • @queued cron manages the tabs in exactly the way you describe (only one file per user though, by design). Have a look in /var/cron/tabs (it may be a different folder on your system). This is not to be modified through other means than by the crontab command though, so anything you build will have to use that. You could obviously also have a system where you have multiple crontabs per user, but you would need to concatenate these to have them activated by cron.
    – Kusalananda
    May 6, 2017 at 16:24
  • My point is: I don't get the design that allows only one file per user. Is it intended, by some acceptable reasons?
    – queuedq
    May 6, 2017 at 16:44
  • 1
    Now I got the point. I misinterpreted the program cron as the only way to run programs periodically, though it actually was just one of the programs that does the thing with a simple interface. I think I should make another program that periodically runs programs, or simply make a folder and make a cron job to run-parts it as you mentioned.
    – queuedq
    May 6, 2017 at 17:32
1

Your current cron implementation probably supports /etc/cron.d, where jobs have an additional "run as" user specified after the regular time fields and before the command. So, just adjust your interface to create files in that directory like regular cron entries with the username (perhaps extracted from the loginuid) prepended after the fifth field. Then you can do one job per file. :)

Look at man 5 crontab https://linux.die.net/man/5/crontab

Note that the main problem with this is that a user who can edit files in that directory could create a file which runs as root. So, you probably want a wrapper script run via sudo which will validate the input and force the calculated username into the generated file. And then got need to make sure you're doing things securely within the script, like not trusting $PATH, etc.

PS, in a shell script: getent passwd $(</proc/self/loginuid)

Honestly, if you're making users know the cron time format, a few extra seconds to teach them to use crontab -e (and set $EDITOR if vi is scary) isn't terribly difficult.

0

If you want a cron entry:

0 * * * * my_script

then I recommend you think of a separate name for your cron management function, like, "cron_mgmt", whose first argument is a SWITCH in a CASE statement:

cron_mgmt () {

    case $1 in
    add ) ... ;;
    list ) ...;;
    remove ) ...
    help ) ...
    * )  echo "You need help. Here it is"
         cron_mgmt help
         ;;
    esac
}   
0

Rather than roll your own interface, keep it simple. To install a cron job with a certain schedule, place it in a certain directory. To remove it, remove it from the directory. You don't need to provide users with any tools to do that, just tell them which directory to put their job in.

This is how most Linux distributions manage system cron jobs that can be installed by individual packages. Debian, Fedora, Arch Linux and others ship a tool called run-parts whose job is to run the scripts in a particular directory one by one. The different implementations of run-parts have different rules on exactly which file names are acceptable for scripts. If you don't want to depend on this, you can roll your own:

for x in /path/to/daily-jobs/*; do
  if [ -x "$x" ]; then
    case "${x##*/}" in
      *[!-0-9A-Z_a-z]*) :;; # skip file names containing "weird" characters
      *) "$x";;
    esac
  fi
done

If you want to allow users to specify their own schedule, then this approach won't work. You need to rebuild the crontab file each time a new job is submitted. This can be done, but is complicated to set up reliably. However, if users can specify their own schedule, then there's little reason to add another interface on top of crontab -e, or a GUI that calls crontab -e under the hood.

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .