445

Does crontab have an argument for creating cron jobs without using the editor (crontab -e). If so, What would be the code create a cronjob from a Bash script?

Raúl Roa
  • 10,841
  • 13
  • 45
  • 62
  • 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:20
  • 8
    Sadly, most top answers here are just showing how to modify crontab -- albeit in reasonably safe ways -- but I think it's overall the wrong approach. Better, safer and simpler is to drop a file into {{cron.d}}, and there are (currently) low-vote answers explaining how to do that if you look down further. – gregmac Apr 12 '18 at 18:49
  • @gregmac, thanks. I believe mine resembles that comment... tried to explain what was happening and where it could live. – BradChesney79 Apr 26 '21 at 15:20

20 Answers20

608

You can add to the crontab as follows:

#write out current crontab
crontab -l > mycron
#echo new cron into cron file
echo "00 09 * * 1-5 echo hello" >> mycron
#install new cron file
crontab mycron
rm mycron

Cron line explaination

* * * * * "command to be executed"
- - - - -
| | | | |
| | | | ----- Day of week (0 - 7) (Sunday=0 or 7)
| | | ------- Month (1 - 12)
| | --------- Day of month (1 - 31)
| ----------- Hour (0 - 23)
------------- Minute (0 - 59)

Source nixCraft.

Jean-Luc Barat
  • 947
  • 8
  • 18
dogbane
  • 242,394
  • 72
  • 372
  • 395
  • 14
    You should use tempfile or mktemp – Paweł Polewicz May 19 '09 at 10:36
  • @dogbane i have a doubt. Why "rm mycron" is used here?? – Satheesh Nov 27 '12 at 09:53
  • `mycron` is a temporary file used to store the contents of the crontab. You can call it anything you like. – dogbane Nov 29 '12 at 08:29
  • 178
    (crontab -l ; echo "00 09 * * 1-5 echo hello") | crontab - – Edo Nov 04 '13 at 13:44
  • 40
    `(crontab -l ; echo "00 09 * * 1-5 echo hello") | crontab -` - easier to copy Edo's answear – WBAR Jun 28 '16 at 04:18
  • 8
    I'd suggest to change that `;` with `&&` as a safe-guard against the case if `crontab -l` fails. so, like: `(crontab -l && echo "0 0 0 0 0 some entry") | crontab -` – MacUsers Jul 06 '16 at 08:43
  • In which file is the line writed in with this ? for example for daily task ? – Jean-Luc Barat Jul 09 '16 at 13:28
  • @Edo, hi, what do i do if i want to make sure no same cronjob are added twice? – moeseth Dec 29 '16 at 18:13
  • 3
    @moeseth I haven't tried (I'm from mobile) but something like this should work `(crontab -l && echo "0 0 0 0 0 some entry" | sort | uniq) | crontab -` – Edo Dec 29 '16 at 18:50
  • 15
    @MacUsers, `crontab -l` fails if there is no crontab, so using `&&` makes it impossible for the script to add the first entry to the crontab. – JPhi1618 Apr 06 '17 at 20:34
  • @JPhi1618, that's very true. I took the liberty to think an existing crontab is already there. Thanks for pointing out. – MacUsers Apr 28 '17 at 17:22
  • Arf, @MacUsers (and others) do you have a suggestion to make this compliant with the first entry of the crontab too? – Hadrien TOMA May 31 '17 at 06:52
  • 6
    Found it but by bypassing the safe guard : `(crontab -l 2>/dev/null ; echo "0 0 0 0 0 some entry") | crontab -` – Hadrien TOMA May 31 '17 at 07:09
  • 1
    This will avoid duplicates: `! (crontab -l | grep -q "SCRIPT_FILENAME") && (crontab -l; echo "20 10 * * * SCRIPT_FILENAME") | crontab -` – garsax Sep 30 '19 at 14:10
345

You may be able to do it on-the-fly

crontab -l | { cat; echo "0 0 0 0 0 some entry"; } | crontab -

crontab -l lists the current crontab jobs, cat prints it, echo prints the new command and crontab - adds all the printed stuff into the crontab file. You can see the effect by doing a new crontab -l.

Thomas Weller
  • 43,638
  • 16
  • 101
  • 185
TheBonsai
  • 12,907
  • 4
  • 20
  • 14
  • 3
    thanks, could you explain the syntax, or point to a url which explains it? – sites May 27 '14 at 22:31
  • 10
    `crontab -l` lists the current crontab jobs, `cat` prints it, `echo` prints the new command and `crontab -` adds all the printed stuff into the crontab file. You can see the effect by doing a new `crontab -l` – nha Jul 25 '14 at 15:24
  • 4
    Works a treat for me. If the user has no existing crontab you'll see `no crontab for `, but it works anyway. – fazy Nov 07 '14 at 11:52
  • Per the example in the comment above, there's no reason for the cat – storm_m2138 Jul 15 '15 at 23:54
  • 25
    doesn't work on my Amazon EC2 instance. Instead, `(crontab -l ; echo "00 09 * * 1-5 echo hello") | crontab -` works. – Stéphane Bruckert Nov 04 '15 at 13:39
  • 11
    This looks like a candidate for a [UUcatA](http://porkmail.org/era/unix/award.html#cat). – ceving Jan 20 '16 at 13:16
  • 2
    you may want to pipe through "sort | uniq" before the last command, to avoid duplicating entries in the crontab (this can change the line order though) – MoonCactus Oct 13 '17 at 07:40
101

This shorter one requires no temporary file, it is immune to multiple insertions, and it lets you change the schedule of an existing entry.

Say you have these:

croncmd="/home/me/myfunction myargs > /home/me/myfunction.log 2>&1"
cronjob="0 */15 * * * $croncmd"

To add it to the crontab, with no duplication:

( crontab -l | grep -v -F "$croncmd" ; echo "$cronjob" ) | crontab -

To remove it from the crontab whatever its current schedule:

( crontab -l | grep -v -F "$croncmd" ) | crontab -

Notes:

  • grep -F matches the string literally, as we do not want to interpret it as a regular expression
  • We also ignore the time scheduling and only look for the command. This way; the schedule can be changed without the risk of adding a new line to the crontab
MoonCactus
  • 1,256
  • 1
  • 9
  • 9
  • @moeseth: can you elaborate? – MoonCactus Dec 31 '16 at 16:02
  • you should try it on your computer – moeseth Jan 01 '17 at 13:06
  • 2
    @moeseth:I obviously tried it on my computer (linux). I would not suggest random non working stuff :/ – MoonCactus Feb 01 '17 at 09:48
  • 3
    By the way, 27 people cannot be wrong (as of today). So please describe why it is not working for your setup, so we can help. – MoonCactus Feb 01 '17 at 09:53
  • Interestingly I regularly get downvotes on this answer. Will someone tell me why? – MoonCactus Mar 14 '17 at 09:04
  • 2
    It might be OSX problem. – moeseth Mar 15 '17 at 06:06
  • @moeseth if you get more info, I would gladly try to cooperate to make it work on any unix. I think that the commands I used are pretty independent of the shell (i.e. not a bashism). – MoonCactus Mar 15 '17 at 08:12
  • I cannot understand why your script won't work if crontab is empty. This only happens with non interactive shell on my CI/CD: the same script works fine when run directly from the host (interactive shell). – a.barbieri Feb 06 '20 at 18:25
  • This command won't work if your bash script has `set -e` and crontab list is empty. This happens because `crontab -l | grep -v -F "$croncmd"` exists with`1` therefore stopping immediately the script. To avoid that you can update the line this way `( crontab -l | grep -v -F "$COMMAND" || : ; echo "$JOB" ) | crontab -`. The `|| :` will ensure that `grep` doesn't stop the script if crontab is empty. More on `:` [here](https://stackoverflow.com/a/3224910/1498118). – a.barbieri Feb 07 '20 at 10:36
  • Good catch. Indeed, you can also cowardly just issue a `crontab -e` once and foremost in order to make sure it pre-exists ;) – MoonCactus Feb 07 '20 at 19:30
41

Thanks everybody for your help. Piecing together what I found here and elsewhere I came up with this:

The Code

command="php $INSTALL/indefero/scripts/gitcron.php"
job="0 0 * * 0 $command"
cat <(fgrep -i -v "$command" <(crontab -l)) <(echo "$job") | crontab -

I couldn't figure out how to eliminate the need for the two variables without repeating myself.

command is obviously the command I want to schedule. job takes $command and adds the scheduling data. I needed both variables separately in the line of code that does the work.

Details

  1. Credit to duckyflip, I use this little redirect thingy (<(*command*)) to turn the output of crontab -l into input for the fgrep command.
  2. fgrep then filters out any matches of $command (-v option), case-insensitive (-i option).
  3. Again, the little redirect thingy (<(*command*)) is used to turn the result back into input for the cat command.
  4. The cat command also receives echo "$job" (self explanatory), again, through use of the redirect thingy (<(*command*)).
  5. So the filtered output from crontab -l and the simple echo "$job", combined, are piped ('|') over to crontab - to finally be written.
  6. And they all lived happily ever after!

In a nutshell:

This line of code filters out any cron jobs that match the command, then writes out the remaining cron jobs with the new one, effectively acting like an "add" or "update" function. To use this, all you have to do is swap out the values for the command and job variables.

Stoutie
  • 1,684
  • 20
  • 15
  • 5
    For the benefit of others reading, the advantage of this approach is that you can run it multiple times without worrying about duplicate entries in the crontab (unlike all the other solutions). That's because of the `fgrep -v` – aleemb Apr 21 '14 at 11:20
  • 5
    If you prefer the traditional left-to-right way of piping things, replace the last line with: `crontab -l | fgrep -i -v "$command" | { cat; echo "$job"; } | crontab -l` – Antoine Lizée Jun 24 '15 at 15:05
  • 1
    @AntoineLizée, you answer has extra "l" in the end, which shouldn't be there. – Henry Apr 01 '19 at 15:59
28

EDIT (fixed overwriting):

cat <(crontab -l) <(echo "1 2 3 4 5 scripty.sh") | crontab -
duckyflip
  • 14,979
  • 5
  • 31
  • 36
19

There have been a lot of good answers around the use of crontab, but no mention of a simpler method, such as using cron.

Using cron would take advantage of system files and directories located at /etc/crontab, /etc/cron.daily,weekly,hourly or /etc/cron.d/:

cat > /etc/cron.d/<job> << EOF
SHELL=/bin/bash 
PATH=/sbin:/bin:/usr/sbin:/usr/bin 
MAILTO=root HOME=/  
01 * * * * <user> <command>
EOF

In this above example, we created a file in /etc/cron.d/, provided the environment variables for the command to execute successfully, and provided the user for the command, and the command itself. This file should not be executable and the name should only contain alpha-numeric and hyphens (more details below).

To give a thorough answer though, let's look at the differences between crontab vs cron/crond:

crontab -- maintain tables for driving cron for individual users

For those who want to run the job in the context of their user on the system, using crontab may make perfect sense.

cron -- daemon to execute scheduled commands

For those who use configuration management or want to manage jobs for other users, in which case we should use cron.

A quick excerpt from the manpages gives you a few examples of what to and not to do:

/etc/crontab and the files in /etc/cron.d must be owned by root, and must not be group- or other-writable. In contrast to the spool area, the files under /etc/cron.d or the files under /etc/cron.hourly, /etc/cron.daily, /etc/cron.weekly and /etc/cron.monthly may also be symlinks, provided that both the symlink and the file it points to are owned by root. The files under /etc/cron.d do not need to be executable, while the files under /etc/cron.hourly, /etc/cron.daily, /etc/cron.weekly and /etc/cron.monthly do, as they are run by run-parts (see run-parts(8) for more information).

Source: http://manpages.ubuntu.com/manpages/trusty/man8/cron.8.html

Managing crons in this manner is easier and more scalable from a system perspective, but will not always be the best solution.

Mike Mackintosh
  • 13,156
  • 6
  • 54
  • 83
8

So, in Debian, Ubuntu, and many similar Debian based distros...

There is a cron task concatenation mechanism that takes a config file, bundles them up and adds them to your cron service running.

You can put a file under the /etc/cron.d/somefilename where somefilename is whatever you want.

sudo echo "0,15,30,45 * * * * ntpdate -u time.nist.gov" >> /etc/cron.d/vmclocksync

Let's disassemble this:

sudo - because you need elevated privileges to change cron configs under the /etc directory

echo - a vehicle to create output on std out. printf, cat... would work as well

" - use a doublequote at the beginning of your string, you're a professional

0,15,30,45 * * * * - the standard cron run schedule, this one runs every 15 minutes

ntpdate -u time.nist.gov - the actual command I want to run

" - because my first double quotes needs a buddy to close the line being output

>> - the double redirect appends instead of overwrites*

/etc/cron.d/vmclocksync - vmclocksync is the filename I've chosen, it goes in /etc/cron.d/


* if we used the > redirect, we could guarantee we only had one task entry. But, we would be at risk of blowing away any other rules in an existing file. You can decide for yourself if possible destruction with > is right or possible duplicates with >> are for you. Alternatively, you could do something convoluted or involved to check if the file name exists, if there is anything in it, and whether you are adding any kind of duplicate-- but, I have stuff to do and I can't do that for you right now.

BradChesney79
  • 606
  • 7
  • 15
  • I had a long running VM where the host OS would go to sleep-- the time wasn't critical on the VM, but it would start to get really out of whack to where it wasn't even the right day anymore. – BradChesney79 May 13 '21 at 17:16
7

Chances are you are automating this, and you don't want a single job added twice. In that case use:

__cron="1 2 3 4 5 /root/bin/backup.sh"
cat <(crontab -l) |grep -v "${__cron}" <(echo "${__cron}")

This only works if you're using BASH. I'm not aware of the correct DASH (sh) syntax.

Update: This doesn't work if the user doesn't have a crontab yet. A more reliable way would be:

(crontab -l ; echo "1 2 3 4 5 /root/bin/backup.sh") | sort - | uniq - | crontab - 

Alternatively, if your distro supports it, you could also use a separate file:

echo "1 2 3 4 5 /root/bin/backup.sh" |sudo tee /etc/crond.d/backup

Found those in another SO question.

kvz
  • 4,387
  • 1
  • 33
  • 30
6

For a nice quick and dirty creation/replacement of a crontab from with a BASH script, I used this notation:

crontab <<EOF
00 09 * * 1-5 echo hello
EOF
Gibbsoft
  • 759
  • 6
  • 5
5

A variant which only edits crontab if the desired string is not found there:

CMD="/sbin/modprobe fcpci"
JOB="@reboot $CMD"
TMPC="mycron"
grep "$CMD" -q <(crontab -l) || (crontab -l>"$TMPC"; echo "$JOB">>"$TMPC"; crontab "$TMPC")
Bartłomiej Semańczyk
  • 52,820
  • 43
  • 206
  • 318
Gerald Schade
  • 463
  • 5
  • 9
4
echo "0 * * * * docker system prune --force >/dev/null 2>&1" | sudo tee /etc/cron.daily/dockerprune
3

If you're using the Vixie Cron, e.g. on most Linux distributions, you can just put a file in /etc/cron.d with the individual cronjob.

This only works for root of course. If your system supports this you should see several examples in there. (Note the username included in the line, in the same syntax as the old /etc/crontab)

It's a sad misfeature in cron that there is no way to handle this as a regular user, and that so many cron implementations have no way at all to handle this.

chuck
  • 902
  • 6
  • 6
2

My preferred solution to this would be this:

(crontab -l | grep . ; echo -e "0 4 * * * myscript\n") | crontab -

This will make sure you are handling the blank new line at the bottom correctly. To avoid issues with crontab you should usually end the crontab file with a blank new line. And the script above makes sure it first removes any blank lines with the "grep ." part, and then add in a new blank line at the end with the "\n" in the end of the script. This will also prevent getting a blank line above your new command if your existing crontab file ends with a blank line.

gsus
  • 21
  • 2
2

Bash script for adding cron job without the interactive editor. Below code helps to add a cronjob using linux files.

#!/bin/bash

cron_path=/var/spool/cron/crontabs/root

#cron job to run every 10 min.
echo "*/10 * * * * command to be executed" >> $cron_path

#cron job to run every 1 hour.
echo "0 */1 * * * command to be executed" >> $cron_path
akash
  • 576
  • 3
  • 14
  • 1
    I know it's been a loooong time, but this is still the only fine answer, since all the most voted ones remove the old crons instead of just append the new one. – afe Mar 28 '19 at 08:22
  • 1
    This one doesn't install a cron. It just appends it to a file. You will have to somehow notify cron process to install the new entry. – Apoorv Parijat May 25 '19 at 18:03
1

Here is a bash function for adding a command to crontab without duplication

function addtocrontab () {
  local frequency=$1
  local command=$2
  local job="$frequency $command"
  cat <(fgrep -i -v "$command" <(crontab -l)) <(echo "$job") | crontab -
}
addtocrontab "0 0 1 * *" "echo hello"
Klas Mellbourn
  • 35,589
  • 18
  • 119
  • 143
1
CRON="1 2 3 4 5 /root/bin/backup.sh" 
cat < (crontab -l) |grep -v "${CRON}" < (echo "${CRON}")

add -w parameter to grep exact command, without -w parameter adding the cronjob "testing" cause deletion of cron job "testing123"

script function to add/remove cronjobs. no duplication entries :

cronjob_editor () {         
# usage: cronjob_editor '<interval>' '<command>' <add|remove>

if [[ -z "$1" ]] ;then printf " no interval specified\n" ;fi
if [[ -z "$2" ]] ;then printf " no command specified\n" ;fi
if [[ -z "$3" ]] ;then printf " no action specified\n" ;fi

if [[ "$3" == add ]] ;then
    # add cronjob, no duplication:
    ( crontab -l | grep -v -F -w "$2" ; echo "$1 $2" ) | crontab -
elif [[ "$3" == remove ]] ;then
    # remove cronjob:
    ( crontab -l | grep -v -F -w "$2" ) | crontab -
fi 
} 
cronjob_editor "$1" "$2" "$3"

tested :

$ ./cronjob_editor.sh '*/10 * * * *' 'echo "this is a test" > export_file' add
$ crontab  -l
$ */10 * * * * echo "this is a test" > export_file
speefak
  • 11
  • 1
  • If you have the same command in the crontab twice (running at different times) the remove will delete both lines. – Michaelkay Dec 20 '18 at 12:10
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
Anh Quach
  • 11
  • 1
0

script function to add cronjobs. check duplicate entries,useable expressions * > "

cronjob_creator () {         
# usage: cronjob_creator '<interval>' '<command>'

  if [[ -z $1 ]] ;then
    printf " no interval specified\n"
elif [[ -z $2 ]] ;then
    printf " no command specified\n"
else
    CRONIN="/tmp/cti_tmp"
    crontab -l | grep -vw "$1 $2" > "$CRONIN"
    echo "$1 $2" >> $CRONIN
    crontab "$CRONIN"
    rm $CRONIN
fi
}

tested :

$ ./cronjob_creator.sh '*/10 * * * *' 'echo "this is a test" > export_file'
$ crontab  -l
$ */10 * * * * echo "this is a test" > export_file

source : my brain ;)

0

For testing when your cron will be executed I recommend

https://crontab.guru/

It shows the result of your setup in human readable format. As well you can hit random button and you will get examples.

0

No, there is no option in crontab to modify the cron files.

You have to: take the current cron file (crontab -l > newfile), change it and put the new file in place (crontab newfile).

If you are familiar with perl, you can use this module Config::Crontab.

LLP, Andrea

andcoz
  • 2,172
  • 14
  • 23