158

I want to be able to programatically add a new cron job, what is the best way to do this?

From my research, it seems I could dump the current crontab and then append a new one, piping that back into crontab:

(crontab -l ; echo "0 * * * * wget -O - -q http://www.example.com/cron.php") | crontab -

Is there a better way?

6
  • 3
    Your solution seems like a good one. Commented Mar 5, 2009 at 2:31
  • On Solaris just remove the dash for the last crontab. You can add a grep to avoid adding a line already there. Commented May 31, 2012 at 16:46
  • Possible duplicate of How to create a cron job using Bash Commented Jul 30, 2017 at 7:12
  • curly braces instead of parentheses will do it without spawning a process. Make sure to keep spaces around the braces, { ... ; }. Commented Apr 4, 2019 at 8:18
  • @davidmytton - would you consider changing your selected answer to that from MarkR - i think the consensus is that it is the better answer to the question and will improve this stackoverflow entry for others Commented Apr 13, 2021 at 16:46

21 Answers 21

170

The best way if you're running as root, is to drop a file into /etc/cron.d

if you use a package manager to package your software, you can simply lay down files in that directory and they are interpreted as if they were crontabs, but with an extra field for the username, e.g.:

Filename: /etc/cron.d/per_minute

Content: * * * * * root /bin/sh /home/root/script.sh

Sign up to request clarification or add additional context in comments.

9 Comments

Just make sure the version of cron in use supports /etc/cron.d/. Most modern Linux distributions do.
One potential caveat to this is the frequency at which cron picks up new additions to this folder (once per hour). If you're expecting your job to begin running right away, beware.
@JonathanK, do you happen to know if cron can be asked to re-scan /etc/cron.d/*? It's hard to know if something is working if one has to wait for an hour to check!
At least on CentOS & RHEL, "The cron daemon checks the /etc/crontab file, the /etc/cron.d/ directory, and the /var/spool/cron/ directory every minute for any changes." centos.org/docs/5/html/5.2/Deployment_Guide/… access.redhat.com/documentation/en-US/Red_Hat_Enterprise_Linux/…
how should perms set for this file? chmod +x /etc/cron.d/per_minute ?
|
129

OP's solution has a bug, it might allow entries to be added twice, use below to fix.

(crontab -l ; echo "0 * * * * your_command") | sort - | uniq - | crontab -

14 Comments

This is actually a better if not described solution. It ensures the command is not added twice to crontab.
To ensure uniqueness, just sort before uniq, e.g.: (crontab -l ; echo "0 * * * * your_command") | sort - | uniq - | crontab -
No need for uniq. Use the -u option on sort.
This has a (small but dangerous) race condition.
how to avoid no crontab for user when I first edit crontab.
|
34

To Add something to cron

(crontab -l ; echo "0 * * * * hupChannel.sh") 2>&1 | grep -v "no crontab" | sort | uniq | crontab -

To remove this from cron

(crontab -l ; echo "0 * * * * hupChannel.sh") 2>&1 | grep -v "no crontab" | grep -v hupChannel.sh |  sort | uniq | crontab -

hope would help someone

2 Comments

This is exactly what I was looking for I would just add this: | grep -v '^#' | to filter out the comments
You actually shouldn't need 2>&1 | grep -v "no crontab" because when there is no crontab, the output line crontab: no crontab for... is sent to stderr. There's no reason to capture that output, send it to stdout, and then filter it out using grep. If your goal is to avoid seeing crontab: no crontab for... in your output, then use 2> /dev/null | sort.....
14

Most of the solutions here are for adding lines to the crontab. If you need more control, you'll want to be able to control the entire contents of the crontab.

You can use piping to do this pretty elegantly.

To completely rewrite the crontab, do

echo "2 2 2 2 2 /bin/echo foobar" |crontab -

This should be easy to combine with other answers described here like

crontab -l | <something> | tee |crontab -

Or, if you have the contents in a file, it is even simpler

cat <file> |crontab -

Comments

6

If you're planning on doing it for a run-once scenario for just wget'ing something, take a look at 'at'

Comments

6

Assuming that there is already an entry in your crontab, the following command should work relatively well. Note that the $CMD variable is only there for readability. Sorting before filtering duplicates is important, because uniq only works on adjacent lines.

CMD='wget -O - -q http://www.example.com/cron.php"'
(crontab -l ; echo "0 * * * * $CMD") | sort | uniq | crontab -

If you currently have an empty crontab, you will receive the following error to stderr:

no crontab for user

If you want to avoid this, you can add a little bit of complexity add do something like this:

(crontab -l ; echo "0 * * * * $CMD") 2>&1 | sed "s/no crontab for $(whoami)//"  | sort | uniq | crontab -

Comments

6

Simply change the editor to tee command:

export EDITOR="tee"
echo "0 * * * * /bin/echo 'Hello World'" | crontab -e

1 Comment

Beware - this wipes any existing entries.
5

Here's another one-liner way, that avoids duplicates

(crontab -l 2>/dev/null | fgrep -v "*/1 *  *  *  * your_command"; echo "*/1 *  *  *  * your_command") | crontab -

And here's a way to do JohnZ's answer and avoid no crontab for user message, or if you need to operate in a set -eu type environment and can't have anything return a failure (in which case the 2>/dev/null part is optional):

( (crontab -l 2>/dev/null || echo "")  ; echo "0 * * * * your_command") | sort -u - | crontab -

Or if you want to split things up so that they're more readable:

new_job="0 * * * * your_command"
preceding_cron_jobs=$(crontab -l || echo "")
(echo "$preceding_cron_jobs" ; echo "$new_job") | sort - | uniq - | crontab -

Or optionally remove any references to your_command (ex: if the schedule has changed, you only want it ever cron'ed once). In this case we no longer need uniq (added bonus, insertion order is also preserved):

new_job="0 * * * * your_command"
preceding_cron_jobs=$(crontab -l || echo "")
preceding_cron_jobs=$(echo "$preceding_cron_jobs" | grep -v your_command )
(echo "$preceding_cron_jobs" ; echo "$new_job") | crontab -

Comments

4

man crontab is also useful:

CRONTAB(1)

NAME

   crontab - manipulate per-user crontabs (Dillon's Cron)

SYNOPSIS

   crontab file [-u user] - replace crontab from file

   crontab - [-u user] - replace crontab from stdin

   crontab -l [user] - list crontab for user

1 Comment

The part that says "replace crontab from stdin" is actually half an answer :-)
2
function cronjob_exists($command){

    $cronjob_exists=false;

    exec('crontab -l', $crontab);


    if(isset($crontab)&&is_array($crontab)){

        $crontab = array_flip($crontab);

        if(isset($crontab[$command])){

            $cronjob_exists=true;

        }

    }
    return $cronjob_exists;
}

function append_cronjob($command){

    if(is_string($command)&&!empty($command)&&cronjob_exists($command)===FALSE){

        //add job to crontab
        exec('echo -e "`crontab -l`\n'.$command.'" | crontab -', $output);


    }

    return $output;
}

    append_cronjob('* * * * * curl -s http://localhost/cron/test.php');

Comments

2

Adding to JohnZ's answer, here's the syntax to schedule as root if you are a sudoer:

(sudo crontab -l ; echo "0 * * * * your_command") | sort - | uniq - | sudo crontab -

Comments

1

This would check to ensure that your command doesn't already exist before adding it.

crontab -l 2>/dev/null | grep -q '/path/to/script' || echo "5 * * * * /path/to/script" | crontab -

Cheers.

Comments

1

Better way is to use Ansible playbook with the following task:

---
- name: Deploy cron
  hosts: all
  gather_facts: false
  tasks:
    - name: Update Cron job
      ansible.builtin.cron:
        name: Run Drupal Cron Job
        user: www-data
        minute: "0"
        job: "/var/www/html/drupal/vendor/bin/drush core:cron"

The above solution uses drush core:cron, since recent Drupal's cron calls require keys (which had to be extracted then hardcoded).

See: Configuring cron jobs using the cron command.

Then use ansible-playbook -i hosts deploy.yml to run it, where hosts file is:

echo "[all]\n\192.168.0.123   ansible_connection=ssh    ansible_user=root" > hosts

where 192.168.0.123 is your host to deploy.

You can use it as part of your deploy scripts or put it in your Jenkinsfile as part of your deploy pipeline.

Comments

0

also you can add your tasks to /etc/cron.*/

Comments

0

Piping stdout into crontab didn't install the new crontab for me on macOS, so I found this solution instead, using the tee editor in a sub shell:

(EDITOR=tee && (crontab -l ; echo "@daily ~/my-script.sh" ) | uniq - | crontab -e)

Comments

0

If you want the task to run as a user:

crontab -l | { cat; echo "@weekly what_you_want_to_execute"; } | crontab -

If you want the task to run with privileges:

sudo crontab -l | { cat; echo "@weekly what_you_want_to_execute"; } | sudo crontab -

and check task (with or without 'sudo'):

crontab -l | sed '/^$/d; /#/d'

Comments

0

Below is what I use in my script

1.

(2>/dev/null crontab -l ; echo "0 3 * * * /usr/local/bin/certbot-auto renew") | crontab -
cat <(crontab -l 2>/dev/null) <(echo "0 3 * * * /usr/local/bin/certbot-auto renew") | crontab -

#write out current crontab

crontab -l > mycron 2>/dev/null

#echo new cron into cron file

echo "0 3 * * * /usr/local/bin/certbot-auto renew" >> mycron

#install new cron file

crontab mycron

rm mycron

Comments

0

To have flexibility when changing the command in the future you can do this (works best with an update script)

MARK=SOME_UNIQUE_MARK_TEXT
LINE="This is the command # $MARK"
# NOTE: I'm using -e because I might want to avoid weird bash expansions for '*' or '$' and place:
# \x2A instead of *
# \x24 instead of $
( crontab -l | grep -v $MARK ; echo -e "0 * * * *" $LINE ) | crontab -

In this way I can update an old crontab command which no longer serves current purpose.

Basically I get the current crontab, I eliminate the line containing the MARK (grep -v) and replace it with the new one by appending it at the end of the crontab file via echo -e. In this particular case I want it executed every hour at minute 0 0 * * * * as it can be seen from here.

Comments

0

I'm adding a solutions similar to @JohnZ's that does not add duplicates while still preserving the existing order of crontab. We have quite a few jobs that are heavily commented and we need to preserve that structure.

$ { \
    crontab -l; \
    echo "*/1  * * * *   echo job one"  &>/dev/null; \
    echo "*/30 * * * *   echo job two"  &>/dev/null; \
  } | awk '(/^#/ || !a[$0]++)' | crontab -

Comments

-2

You could also edit the cron table text file directly, but your solution seems perfectly acceptable.

3 Comments

I would be leery of this due to concerns of the chance of concurrent edits causing file corruption. Using the command-line crontab command should avoid that problem.
Craig, without more research I wouldn't be sure that the command line version is atomic and race-condition-safe. Probably "quite safe" anyway.
"Editing the file directly" is not feasible without root access (provided you can figure out where the user's crontab file lives, and how to make sure the cron daemon correctly receives your changes), and "editing" programmatically seems to be what the question wants advice on in the first place.
-28

It's always worked well for me.

You should consider a slightly more sophisticated script that can do three things.

  1. Append a crontab line; assuring that it didn't exist. Adding when it already exists is bad.

  2. Remove the crontab line. Perhaps only warning if it didn't exist.

  3. A combination of the above two features to replace the crontab line.

3 Comments

He asks how and you tell him what?
What if the user does not have a crontab yet?
Please provide an example to answer the question

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.