224

I need to add a cron job thru a script I run to set up a server. I am currently using Ubuntu. I can use crontab -e but that will open an editor to edit the current crontab. I want to do this programmatically.

Is it possible to do so?

miku
  • 181,842
  • 47
  • 306
  • 310
stocked
  • 2,889
  • 4
  • 21
  • 18
  • https://stackoverflow.com/questions/610839/how-can-i-programmatically-create-a-new-cron-job – dskrvk Feb 10 '17 at 00:27
  • 2
    Possible duplicate of [How can I programmatically create a new cron job?](http://stackoverflow.com/questions/610839/how-can-i-programmatically-create-a-new-cron-job) – Twonky Feb 26 '17 at 16:21
  • If you're looking to modify or delete a crontab entry, see my solution below. – Brian Smith Mar 09 '18 at 18:03

14 Answers14

530

Here's a one-liner that doesn't use/require the new job to be in a file:

(crontab -l 2>/dev/null; echo "*/5 * * * * /path/to/job -with args") | crontab -

The 2>/dev/null is important so that you don't get the no crontab for username message that some *nixes produce if there are currently no crontab entries.

Joe Casadonte
  • 15,888
  • 11
  • 45
  • 57
  • 20
    This should be the accepted answer. Just need a way now to check if the one-liner I intend to add is already there or not... – ChrisPrime Dec 14 '14 at 07:12
  • 11
    ...oh wait, here's how to check if something is in my user's crontab before I add it with a script: http://stackoverflow.com/a/14451184/3686125 – ChrisPrime Dec 14 '14 at 07:17
  • 3
    if this script is intended as a command to be repeated and modify an existing cron task, it could be nice to replace the existing line in crontab. several markers can be used to control different cron tasks (control-marker-1, control-marker-2, etc...): (crontab -l 2>/dev/null | grep -v control-marker-1; echo '*/5 * * * * /path/to/job -with args #control-marker-1') | crontab - – chef Dec 25 '16 at 10:39
  • 9
    I found that this was removing existing crontab entries and also I needed to use a different user (root) so I used the following to maintain the existing entries: `echo -e "$(sudo crontab -u root -l)\n* * * * * echo hello > /home/danny/temp.log 2>&1" | sudo crontab -u root -` Hopefully this helps someone – Danny Mar 31 '17 at 10:58
  • What does `*/` mean here? – SOFe Sep 25 '20 at 16:40
  • @SOFe - it's a step value, meaning that you take all of the allowable values ("*") and run the command on any that are evenly divisible by whatever the value is ("/5"). So this would run the command every minute that is evenly divisible by 5 (i.e. 0, 5, 10, 15, 20, 25, 30, 35, 40, 45, 50, 55). If I had done "10-38" it would run @ minutes 16, 24, and 32. See 'man 5 crontab' for more examples. – Joe Casadonte Sep 27 '20 at 13:57
  • I see, thanks. It's strange how come everyone uses that in this page when it's not related to the OP. – SOFe Sep 27 '20 at 14:01
  • On debian, I had not to add the trailing dash for this to work. Pipe says to branch the previous command stdout to stdin. – Stéphane de Luca Sep 09 '21 at 13:21
  • I have tried this method but get the following error when running the script: _"-":23: bad command_ My script contain the following: `#!\bin\bash (crontab -l 2>/dev/null; echo "sleep 60 && @reboot /root/a/myscript") | crontab -` – LEyahoo Dec 28 '21 at 07:36
  • @LEyahoo -- that error is coming from cron itself; you need some kind of time specifier as the first element, e.g. `echo "@reboot sleep 60 && /root/a/myscript"`. I think the sleep would best be done inside of `myscript`, though, making the crontab entry that much simpler. – Joe Casadonte Dec 28 '21 at 12:47
  • +1 This solution is exactly what I need. However it will be good if an explanation could be given on how `2>/dev/null` works. – user3437460 May 19 '22 at 02:26
  • @user3437460 - that is Bash's I/O redirection at work; it takes anything printed to STDERR (file descriptor #2) and redirects it to the "file" `/dev/null`, which is a special device that (essentially) ignores anything written to it. See https://tldp.org/LDP/abs/html/io-redirection.html for more info. – Joe Casadonte May 20 '22 at 04:15
  • 1
    Make sure to so see this below https://stackoverflow.com/a/51497394/699493 if you use if you use "set -e": because this will silently fail in a confusing way. Thanks @Faria! – runamok Jan 23 '23 at 22:25
70

For user crontabs (including root), you can do something like:

crontab -l -u user | cat - filename | crontab -u user -

where the file named "filename" contains items to append. You could also do text manipulation using sed or another tool in place of cat. You should use the crontab command instead of directly modifying the file.

A similar operation would be:

{ crontab -l -u user; echo 'crontab spec'; } | crontab -u user -

If you are modifying or creating system crontabs, those may be manipulated as you would ordinary text files. They are stored in the /etc/cron.d, /etc/cron.hourly, /etc/cron.daily, /etc/cron.weekly, /etc/cron.monthly directories and in the files /etc/crontab and /etc/anacrontab.

Dennis Williamson
  • 346,391
  • 90
  • 374
  • 439
  • Looked promising but trying the second approach (with `echo`), I got "crontab: usage error: file name must be specified for replace." Cron man page shows syntax as `crontab [ -u user ] file`, that is, with a mandatory file name. Is there some trick to get it to accept the piped data instead? – Mark Berry Aug 16 '14 at 20:21
  • 1
    @MarkBerry: Sorry about that. In a pipe, a hyphen must be used to indicate that input is from `stdin`. I'll correct my answer. – Dennis Williamson Aug 18 '14 at 15:23
39

In Ubuntu and many other distros, you can just put a file into the /etc/cron.d directory containing a single line with a valid crontab entry. No need to add a line to an existing file.

If you just need something to run daily, just put a file into /etc/cron.daily. Likewise, you can also drop files into /etc/cron.hourly, /etc/cron.monthly, and /etc/cron.weekly.

IvanGoneKrazy
  • 605
  • 4
  • 4
31

Joe Casadonte's one-liner is perfect, except if you run with set -e, i.e. if your script is set to fail on error, and if there are no cronjobs yet. In that case, the one-liner will NOT create the cronjob, but will NOT stop the script. The silent failure can be very misleading.

The reason is that crontab -l returns with a 1 return code, causing the subsequent command (the echo) not to be executed... thus the cronjob is not created. But since they are executed as a subprocess (because of the parenthesis) they don't stop the script.

(Interestingly, if you run the same command again, it will work: once you have executed crontab - once, crontab -l still outputs nothing, but it doesn't return an error anymore (you don't get the no crontab for <user> message anymore). So the subsequent echo is executed and the crontab is created)

In any case, if you run with set -e, the line must be:

(crontab -l 2>/dev/null || true; echo "*/5 * * * * /path/to/job -with args") | crontab -
Nathan Tuggy
  • 2,237
  • 27
  • 30
  • 38
Faria
  • 311
  • 3
  • 3
23

Crontab files are simply text files and as such can be treated like any other text file. The purpose of the crontab command is to make editing crontab files safer. When edited through this command, the file is checked for errors and only saved if there are none.

crontab [path to file] can be used to specify a crontab stored in a file. Like crontab -e, this will only install the file if it is error free.

Therefore, a script can either directly write cron tab files, or write them to a temporary file and load them with the crontab [path to temp file] command. Writing directly saves having to write a temporary file, but it also avoids the safety check.

cledoux
  • 4,717
  • 1
  • 22
  • 30
  • 2
    For noobs such as myself, note that it is `crontab [path to file]` .. This was definately the best option for me, as it allows more legible code. I use crontab to track parcels and change my desktop wallpaper with the status. When I'm not expecting parcels, it doesn't need to check every hour. That's why I wanted the script to auto-edit the cron frequency. – Rasmus Feb 22 '16 at 12:20
  • 1
    @Rasmus That sounds like an awesome script I'd love to be able to steal. Any chance of sharing via a gist or similar? – cledoux Feb 22 '16 at 22:24
  • Just FYI that `crontab [path to file]` doesn't necessarily need a "temporary" file, it can load any file that is properly formatted in a crontab layout. This is the approach we are using with SlickStack, so that the crontab template can be updated (downloaded) anytime and then reinstalled: https://github.com/littlebizzy/slickstack/blob/master/00-crontab.txt – Jesse Nickles Aug 13 '20 at 12:28
21

Even more simple answer to you question would be:

echo "0 1 * * * /root/test.sh" | tee -a /var/spool/cron/root

You can setup cronjobs on remote servers as below:

#!/bin/bash
servers="srv1 srv2 srv3 srv4 srv5"
for i in $servers
  do
  echo "0 1 * * * /root/test.sh" | ssh $i " tee -a /var/spool/cron/root"
done

In Linux, the default location of the crontab file is /var/spool/cron/. Here you can find the crontab files of all users. You just need to append your cronjob entry to the respective user's file. In the above example, the root user's crontab file is getting appended with a cronjob to run /root/test.sh every day at 1 AM.

Benjamin Hodgson
  • 42,952
  • 15
  • 108
  • 157
ganesh pathak
  • 219
  • 2
  • 2
9

Cron jobs usually are stored in a per-user file under /var/spool/cron

The simplest thing for you to do is probably just create a text file with the job configured, then copy it to the cron spool folder and make sure it has the right permissions (600).

Shardj
  • 1,800
  • 2
  • 17
  • 43
Jason Stelzer
  • 354
  • 1
  • 2
  • 32
    Modifying the files directly in /var/spool/cron is frowned upon. In fact, if you look at the files there, they will usually contain warnings such as "DO NOT EDIT THIS FILE" as the first line. – Jared Dec 10 '15 at 20:53
  • 14
    @Jared While I quite agree with the idea, saying it "is frowned upon" doesn't help much. Rather tell which _other_ file should be edited, if any, or explain the risks of editing files manually. I'm planning to create some cron jobs via automated command line, and if editing this file is both the only option, and without significant side effects, I don't see why I shouldn't use it. – Balmipour Jan 20 '17 at 11:20
  • 62
    Scroll down for the real answer. – John Red Feb 18 '17 at 04:53
  • 1
    In my opinion, this answer is way better: https://stackoverflow.com/a/610860/2681752 – galaux Jul 09 '17 at 12:41
  • 2
    I took this approach and regretted it. On RHEL, the directory /var/spool/cron is not world executable, so users can't traverse the directory and edit their files manually. If you make /var/spool/cron world executable, your friendly local sysadmin will be mad at you, plus the permission change can be overwritten if the cronie package is ever reinstalled or updated. – ellipse-of-uncertainty Nov 13 '17 at 22:32
  • @Jared those warnings are often because there is something like cpanel or plesk on the server, and these may overwrite the cron file(s) at some point. Appreciate that you may have learned that in the last 4 years! – Don Dec 08 '19 at 15:50
7

As a correction to those suggesting crontab -l | crontab -: This does not work on every system. For example, I had to add a job to the root crontab on dozens of servers running an old version SUSE (don't ask why). Old SUSEs prepend comment lines to the output of crontab -l, making crontab -l | crontab - non-idempotent (Debian recognizes this problem in the crontab manpage and patched its version of Vixie Cron to change the default behaviour of crontab -l).

To edit crontabs programmatically on systems where crontab -l adds comments, you can try the following:

EDITOR=cat crontab -e > old_crontab; cat old_crontab new_job | crontab -

EDITOR=cat tells crontab to use cat as an editor (not the usual default vi), which doesn't change the file, but instead copies it to stdout. This might still fail if crontab - expects input in a format different from what crontab -e outputs. Do not try to replace the final crontab - with crontab -e - it will not work.

user2845840
  • 360
  • 3
  • 9
6

Well /etc/crontab just an ascii file so the simplest is to just

 echo "*/15 * * * *   root     date" >> /etc/crontab

which will add a job which will email you every 15 mins. Adjust to taste, and test via grep or other means whether the line was already added to make your script idempotent.

On Ubuntu et al, you can also drop files in /etc/cron.* which is easier to do and test for---plus you don't mess with (system) config files such as /etc/crontab.

Dirk Eddelbuettel
  • 360,940
  • 56
  • 644
  • 725
  • 1
    I think that technically crond is not required to monitor changes to crontab, even if in reality most implementations do, so I'd recommend a call to crontab -e afterwards to prod it. crontab -e honours the EDITOR variable if memory serves, so setting it to /bin/true for the moment should just force the crontab to be re-read. – Ulrich Schwarz Feb 02 '11 at 22:00
  • 1
    True yet on any recent Linux system `crond` *does* monitor, and it certainly does on the OP's stated platform. – Dirk Eddelbuettel Feb 02 '11 at 22:08
  • Only if you're root and want the script to run as root. That might not be desirable in the OPs case. – Keith Feb 02 '11 at 23:02
  • Not so, I have plenty of non-root entries in /etc/crontab. You 'merely' need sudo to append to the file. Anyway, as I stated, there is also /etc/cron.*/ but you also need to be root to write there. – Dirk Eddelbuettel Feb 02 '11 at 23:08
3

Here is how to modify cron a entry without directly editing the cron file (which is frowned upon).

crontab -l -u <user> | sed 's/find/replace/g' | crontab -u <user> -

If you want to remove a cron entry, use this:

crontab -l -u <user> | sed '/find/d' | crontab -u <user> -

I realize this is not what gaurav was asking for, but why not have all the solutions in one place?

Brian Smith
  • 1,443
  • 5
  • 18
  • 24
  • Can you give a fully working example that actually replaces a cron expression? I tried, but it's not working for me. Especially when the scheduled script's full path is used with forwards slashes. – Koder101 Dec 01 '20 at 11:19
  • @Koder101 maybe have a look at https://unix.stackexchange.com/a/33005 for what and how you need to escape when using `sed` – Chris Stenkamp Oct 22 '22 at 22:18
3

It is an approach to incrementally add the cron job:

  ssh USER_NAME@$PRODUCT_IP nohup "echo '*/2 * * * * ping -c2 PRODUCT_NAME.com >> /var/www/html/test.html' | crontab -u USER_NAME -"
Oleksii Kyslytsyn
  • 2,458
  • 2
  • 27
  • 43
2

Another solution to add multiple scripts to crontab at once:

cat <<EOF | crontab -
* * * * * /bin/foo.sh
* * * * * /bin/gaga.sh
EOF
HeyMan
  • 1,529
  • 18
  • 32
1

I have written a crontab deploy tool in python: https://github.com/monklof/deploycron

pip install deploycron

Install your crontab is very easy, this will merge the crontab into the system's existing crontab.

from deploycron import deploycron
deploycron(content="* * * * * echo hello > /tmp/hello")
Nikolay Mihaylov
  • 3,868
  • 8
  • 27
  • 32
monklof
  • 161
  • 1
  • 6
0

the easiest solution is to use echo with >> and then run crontab filename ex:

 ssh $HOST "echo \"* * * * * echo hello >> /var/spool/cron/crontabs/root\"; sleep 2; crontab /var/spool/cron/crontabs/root" 
Chris Catignani
  • 5,040
  • 16
  • 42
  • 49