Writing a shell script to install cron job - linux

This is the first time i am writing a shell script and i have very little information in the given timeline. Though i am reading through different tutorials but i thought to ask what i want here as well.
I want to write a shell script, which on any machine, edit the cronjob, add a new script to be executed at every 15 minutes. so basically i have to add an entry
0,15,30,45 * * * * /home/personal/scripts/cronSqlprocedure.sh
What i want in the shell script
it would first change the permissions/execution rights for cronSqlprocedure.sh
edit existing cron job and add this new entry into it.
If possible, I would like to write cronSqlprocedure through the shell script too, since it requires couple of variables which may varry from system to system.
export ORACLE_HOME=/opt/app/oracle/product/11.2.0/dbhome_1
export PATH=$ORACLE_HOME/bin:$PATH
export ORACLE_SID=HEER
These lines have to be configured for each machine in the cronSqlprocedure.sh.
#!/bin/bash
ORACLE_HOME="/opt/app/oracle/product/11.2.0/dbhome_1"
ORACLE_SID="HEER"
ORACLE_USER="USER1"
ORACLE_PASSWORD="USERPASS"
echo "export ORACLE_HOME=$ORACLE_HOME" >> $PWD/sqlcronprocedure.sh
echo "export PATH=\$ORACLE_HOME/bin:\$PATH" >> $PWD/sqlcronprocedure.sh
echo "export ORACLE_SID=$ORACLE_SID" >> $PWD/sqlcronprocedure.sh
echo "rTmpDir=/tmp" >> $PWD/sqlcronprocedure.sh
echo "sqlplus -s $ORACLE_USER#$ORACLE_SID/$ORACLE_PASSWORD > $rTmpDir/deleteme.txt 2>&1 <<EOF" >> $PWD/sqlcronprocedure.sh
echo " select 1 from dual;" >> $PWD/sqlcronprocedure.sh
echo " execute another_script(1000,14);" >> $PWD/sqlcronprocedure.sh
echo "EOF" >> $PWD/sqlcronprocedure.sh
chmod 755 $PWD/sqlcronprocedure.sh
crontab -l > $PWD/sqlcorn.sh
echo "0,15,30,45 * * * * $PWD/sqlcronprocedure.sh" >> $PWD/sqlcorn.sh
crontab $PWD/sqlcorn.sh

Simple Answer to Original Question
It all seems like routine shell scripting:
# Clobber previous edition of script!
cronscript=$HOME/scripts/cronSqlprocedure.sh
cat <<EOF > $cronscript
export ORACLE_HOME=/opt/app/oracle/product/11.2.0/dbhome_1
export PATH=\$ORACLE_HOME/bin:\$PATH
export ORACLE_SID=HEER
...and whatever else is needed...
EOF
chmod u+x $cronscript
# Add to crontab
tmp=${TMPDIR:-/tmp}/xyz.$$
trap "rm -f $tmp; exit 1" 0 1 2 3 13 15
crontab -l | sed '/cronSqlprocedure.sh/d' > $tmp # Capture crontab; delete old entry
echo "0,15,30,45 * * * * $cronscript" >> $tmp
crontab < $tmp
rm -f $tmp
trap 0
The trap stuff ensures minimum damage if the user decides to interrupt, cleaning up the temporary file. Note that the old version of the script, if any, has already been clobbered. If you wanted to, you could arrange to create the script into another temp file, and only finish the moving when your satisfied. I typically use I/O redirection on the crontab command; you can perfectly well supply the file name as an argument.
Note the escapes on \$ORACLE_HOME and \$PATH that William Pursell correctly pointed out should be present on \$ORACLE_HOME and should (perhaps) be present on \$PATH. You need to decide whether you want to take the cron-provided (totally minimal) value of $PATH (in which case you want the backslash) or whether you want to use the user's current value of $PATH in the cron script. Either could be correct - just be aware of which you choose and why. Remember, the environment provided by cron is always minimal; you will get a setting for PATH, HOME, maybe TZ, probably USER and possibly LOGNAME; that may be all. If you're not sure, try running a crontab entry which captures the environment in a file:
* * * * * env > /tmp/cron.env
You're likely to find that the file is small. Don't forget to remove the entry after testing it.
One good thing that you're to be commended for:
Your script (a) ensures that it sets the environment, and (b) runs a simple command from the crontab entry, leaving the script to do the hard work.
In my view, the entries in the crontab file should indeed be simple like that, invoking a purpose-built script to do the real work.
Critique of Proposed Script in the Revised Question
#!/bin/bash
ORACLE_HOME="/opt/app/oracle/product/11.2.0/dbhome_1"
ORACLE_SID="HEER"
ORACLE_USER="USER1"
ORACLE_PASSWORD="USERPASS"
Thus far, no problem:
echo "export ORACLE_HOME=$ORACLE_HOME" >> $PWD/sqlcronprocedure.sh
echo "export PATH=\$ORACLE_HOME/bin:\$PATH" >> $PWD/sqlcronprocedure.sh
echo "export ORACLE_SID=$ORACLE_SID" >> $PWD/sqlcronprocedure.sh
echo "rTmpDir=/tmp" >> $PWD/sqlcronprocedure.sh
echo "sqlplus -s $ORACLE_USER#$ORACLE_SID/$ORACLE_PASSWORD > $rTmpDir/deleteme.txt 2>&1 <<EOF" >> $PWD/sqlcronprocedure.sh
echo " select 1 from dual;" >> $PWD/sqlcronprocedure.sh
echo " execute prvsapupd(1000,14);" >> $PWD/sqlcronprocedure.sh
echo "EOF" >> $PWD/sqlcronprocedure.sh
This is horribly repetitive, and starting out with append is not good. I would use:
cronscript=$PWD/sqlcronprocedure.sh
{
echo "export ORACLE_HOME=$ORACLE_HOME"
echo "export PATH=\$ORACLE_HOME/bin:\$PATH"
echo "export ORACLE_SID=$ORACLE_SID"
echo "rTmpDir=/tmp"
echo "sqlplus -s $ORACLE_USER#$ORACLE_SID/$ORACLE_PASSWORD > $rTmpDir/deleteme.txt 2>&1 <<EOF"
echo " select 1 from dual;"
echo " execute prvsapupd(1000,14);"
echo "EOF"
} > $cronscript
The { ... } apply the I/O redirection to the enclosed commands. Note that there must be a semi-colon or newline before the }.
chmod 755 $PWD/sqlcronprocedure.sh
Since I have a variable for the file name, I'd use it:
chmod 755 $cronscript
Then we have a problem with repetition here, plus not cleaning up behind ourselves:
crontab -l > $PWD/sqlcorn.sh
echo "0,15,30,45 * * * * $PWD/sqlcronprocedure.sh" >> $PWD/sqlcorn.sh
crontab $PWD/sqlcorn.sh
Thus I'd write:
crontab=sqlcron.sh
crontab -l > $crontab
echo "0,15,30,45 * * * * $cronscript" >> $crontab
crontab $crontab
rm -f $crontab
I still think that trap is not too hard and should be used in any script that creates temporary files; however, it's your mess, not mine. I'm not convinced the $PWD is needed everywhere; I left it in one name and not in the other. If you don't supply a directory path, the $PWD is implied. I also note that you're using a slightly different script name in your proposed full script from the one in the original. As long as the names are self-consistent, there isn't a problem (and using a variable helps ensure consistency), but be careful.
I'm not sure that I'd actually do it this way, but you could also avoid the temporary file using:
{
crontab -l
echo "0,15,30,45 * * * * $cronscript"
} | (sleep 1; crontab -)
This collects the current value and appends the extra line, feeding all that into a script that sleeps for a second (to allow the first part time to complete) before feeding the results back into crontab. There's a question of how reliable is the one second delay, mainly. It's likely fine, but not guaranteed. The temporary file is 100% reliable - I'd use it because it isn't any more complex. (I could use parentheses around the first pair of commands; I could use braces around the second pair of commands, but I'd need to add a semi-colon between the - and the ) that is replaced by }.)
Note that my original proposal was careful to ensure that even if the script was run multiple times, there'd be only one entry in the crontab file for the process. Your variants do not make sure of the idempotency.

I found a similar question: Edit crontab programmatically and force the daemon to refresh
Changing the permissions of the file is typical shell scripting with many resources available.
For the cronjob, you'll want to essentially interface with the crontab program and feed it and entirely new cron file. You can first retrieve already configured cron jobs, add yours to the list, and then call crontab again to give it the new input file.
See the man-page for crontab for more information.

#!/bin/sh
SCRIPT=/home/personal/scripts/cronSqlprocedure.sh
# write the script.
cat > $SCRIPT << 'EOF'
export ORACLE_HOME=/opt/app/oracle/product/11.2.0/dbhome_1
export PATH=$ORACLE_HOME/bin:$PATH
export ORACLE_SID=HEER
EOF
# Make the script executable
chmod +x $SCRIPT
# Add this script to the existing crontab. This relies on your
# sed supporting -i. if it does not, it is probably easiest to
# write a simple script that does the edit and set VISUAL to that script
if ! crontab -l | grep $SCRIPT > /dev/null; then
VISUAL='sed -i -e "\$a\
0,15,30,45 * * * * '$SCRIPT'"' crontab -e
fi

Create a cron.d file such as /etc/cron.d/my-sql-proc
0,15,30,45 * * * * root /home/personal/scripts/cronSqlprocedure.sh
#
# run-as user
Answer by #Patrick from unix.stackexchange.com:
I would recommend using /etc/cron.d over crontab.
You can place files in /etc/cron.d which behave like crontab entries. Though the format is slightly different.
Patrick points out it may not work on all systems, but his answer is both accepted and has the most votes. It works well for me on debian 9 (stretch).
source: https://unix.stackexchange.com/questions/117244/installing-crontab-using-bash-script#117254

Related

How to get Crontab running using a script instead of adding an entry using crontab -e [duplicate]

Does crontab have an argument for creating cron jobs without using the editor (crontab -e)? If so, what would be the code to create a cron job from a Bash script?
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.
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.
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
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
Credit to duckyflip, I use this little redirect thingy (<(*command*)) to turn the output of crontab -l into input for the fgrep command.
fgrep then filters out any matches of $command (-v option), case-insensitive (-i option).
Again, the little redirect thingy (<(*command*)) is used to turn the result back into input for the cat command.
The cat command also receives echo "$job" (self explanatory), again, through use of the redirect thingy (<(*command*)).
So the filtered output from crontab -l and the simple echo "$job", combined, are piped ('|') over to crontab - to finally be written.
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.
EDIT (fixed overwriting):
cat <(crontab -l) <(echo "1 2 3 4 5 scripty.sh") | crontab -
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.
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.
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
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.
echo "0 * * * * docker system prune --force >/dev/null 2>&1" | sudo tee /etc/cron.daily/dockerprune
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")
(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
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.
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.
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
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"
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
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
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 ;)
Say you're logged in as the user "ubuntu", but you want to add a job to a different user's crontab, like "john", for example. You can do the following:
(sudo crontab -l -u john; echo "* * * * * command") | awk '!x[$0]++' | sudo crontab -u john -
Source for most of this solution: https://www.baeldung.com/linux/create-crontab-script
I was having tons of issues trying to add a job to another user's crontab. It kept duplicating crontabs, or just flat-out deleting them. After some testing, though, I'm confident this line of code will append a new job to a specified user's crontab, non-destructively, including not creating a job that already exists.
I wanted to find an example like this, so maybe it helps:
COMMAND="/var/lib/postgresql/backup.sh"
CRON="0 0 * * *"
USER="postgres"
CRON_FILE="postgres-backup"
# At CRON times, the USER will run the COMMAND
echo "$CRON $USER $COMMAND" | sudo tee /etc/cron.d/$CRON_FILE
echo "Cron job created. Remove /etc/cron.d/$CRON_FILE to stop it."

Set a cronjob from perl script [duplicate]

Does crontab have an argument for creating cron jobs without using the editor (crontab -e)? If so, what would be the code to create a cron job from a Bash script?
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.
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.
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
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
Credit to duckyflip, I use this little redirect thingy (<(*command*)) to turn the output of crontab -l into input for the fgrep command.
fgrep then filters out any matches of $command (-v option), case-insensitive (-i option).
Again, the little redirect thingy (<(*command*)) is used to turn the result back into input for the cat command.
The cat command also receives echo "$job" (self explanatory), again, through use of the redirect thingy (<(*command*)).
So the filtered output from crontab -l and the simple echo "$job", combined, are piped ('|') over to crontab - to finally be written.
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.
EDIT (fixed overwriting):
cat <(crontab -l) <(echo "1 2 3 4 5 scripty.sh") | crontab -
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.
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.
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
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.
echo "0 * * * * docker system prune --force >/dev/null 2>&1" | sudo tee /etc/cron.daily/dockerprune
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")
(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
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.
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.
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
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"
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
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
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 ;)
Say you're logged in as the user "ubuntu", but you want to add a job to a different user's crontab, like "john", for example. You can do the following:
(sudo crontab -l -u john; echo "* * * * * command") | awk '!x[$0]++' | sudo crontab -u john -
Source for most of this solution: https://www.baeldung.com/linux/create-crontab-script
I was having tons of issues trying to add a job to another user's crontab. It kept duplicating crontabs, or just flat-out deleting them. After some testing, though, I'm confident this line of code will append a new job to a specified user's crontab, non-destructively, including not creating a job that already exists.
I wanted to find an example like this, so maybe it helps:
COMMAND="/var/lib/postgresql/backup.sh"
CRON="0 0 * * *"
USER="postgres"
CRON_FILE="postgres-backup"
# At CRON times, the USER will run the COMMAND
echo "$CRON $USER $COMMAND" | sudo tee /etc/cron.d/$CRON_FILE
echo "Cron job created. Remove /etc/cron.d/$CRON_FILE to stop it."

crontab not executing complex bash script

SOLVED! add #!/bin/bash at the top of all my scripts in order to make use of bash extensions. Otherwise it restricts itself to POSIX shell syntax. Thanks Barmar!
Also, I'll add that I had trouble with gpg decryption not working from cronjob after I got it executing, and the answer was to add the --no-tty option (no terminal output) to the gpg command.
I am fairly new to linux, so bear with me...
I am able to execute a simple script with crontab -e when logged in as ubuntu:
* * * * * /ngage/extract/bin/echoer.sh
and this bash script simply prints output to a file:
echo "Hello" >> output.txt
But when I try to execute my more complex bash script in exactly the same way, it doesn't work:
* * * * * /ngage/extract/bin/superMasterExtract.sh
This script called into other bash scripts. There are 4 scripts in total, which 3 levels of hierarchy. It goes superMasterExtract > masterExtract > (decrypt, unzip)
Here is the code for superMasterExtract.sh (top level):
shopt -s nullglob # ignore empty file
cd /str/ftp
DIRECTORY='writeable'
for d in */ ; do # for all directories in /str/ftp
if [ -d "$d$DIRECTORY" ]; then # if the directory contains a folder called 'writeable'
files=($d$DIRECTORY/*)
dirs=($d$DIRECTORY/*/)
numdirs=${#dirs[#]}
numFiles=${#files[#]}
((numFiles-=$numdirs))
if [ $numFiles -gt 0 ]; then # if the folder has at least one file in it
bash /ngage/extract/bin/masterExtract.sh /str/ftp ${d:0:${#d} - 1} # execute this masterExtract bash script with two parameters passed in
fi
fi
done
masterExtract.sh:
DATE="$(date +"%m-%d-%Y_%T")"
LOG_FILENAME="log$DATE"
LOG_FILEPATH="/ngage/extract/logs/$2/$LOG_FILENAME"
echo "Log file is $LOG_FILEPATH"
bash /ngage/extract/bin/decrypt.sh $1 $2 $DATE
java -jar /ngage/extract/bin/sftp.jar $1 $2
bash /ngage/extract/bin/unzip.sh $1 $2 $DATE
java -jar /ngage/extract/bin/sftp.jar $1 $2
echo "Log file is $LOG_FILEPATH"
decrypt.sh:
shopt -s nullglob
UPLOAD_FILEPATH="$1/$2/writeable"
DECRYPT_FOLDER="$1/decryptedFiles/$2"
HISTORY_FOLDER="$1/encryptHistory/$2"
DONE_FOLDER="$1/doneFiles/$2"
LOG_FILENAME="log$3"
LOG_FILEPATH="/ngage/extract/logs/$2/$LOG_FILENAME"
echo "DECRYPT_FOLDER=$DECRYPT_FOLDER" >> $LOG_FILEPATH
echo "HISTORY_FOLDER=$HISTORY_FOLDER" >> $LOG_FILEPATH
cd $UPLOAD_FILEPATH
for FILE in *.gpg;
do
FILENAME=${FILE%.gpg}
echo ".done FILE NAME=$UPLOAD_FILEPATH/$FILENAME.done" >> $LOG_FILEPATH
if [[ -f $FILENAME.done ]]; then
echo "DECRYPTING FILE=$UPLOAD_FILEPATH/$FILE INTO $DECRYPT_FOLDER/$FILENAME" >> $LOG_FILEPATH
cat /ngage/extract/.sftpPasswd | gpg --passphrase-fd 0 --output "$DECRYPT_FOLDER/$FILENAME" --decrypt "$FILE"
mv $FILE $HISTORY_FOLDER/$FILE
echo "MOVING FILE=$UPLOAD_FILEPATH/$FILE INTO $HISTORY_FOLDER/$FILE" >> $LOG_FILEPATH
else
echo "Done file not found!" >> $LOG_FILEPATH
fi
done
cd $DECRYPT_FOLDER
for FILE in *
do
mv $FILE $DONE_FOLDER/$FILE
echo "DECRYPTED FILE=$DONE_FOLDER/$FILE" >> $LOG_FILEPATH
done
If anyone has a clue why it refuses to execute my more complicated script, I'd love to hear it. I have also tried setting some environment variables at the beginning of crontab as well:
SHELL=/bin/bash
PATH=/sbin:/bin:/usr/sbin:/usr/local/bin:/usr/bin
MAILTO=jgardnerx85#gmail.com
HOME=/
* * * * * /ngage/extract/bin/superMasterExtract.sh
Note, I don't know that these are the appropriate variables for my installation or my script. I just pulled them off other posts and tried it to no avail. If these aren't the correct environment variables, can someone tell me how I can deduce the right ones for my particular application?
You need to begin your script with
#!/bin/bash
in order to make use of bash extensions. Otherwise it restricts itself to POSIX shell syntax.

How to output the start and stop datetime of shell script (but no other log)?

I am still very new to shell scripting (bash)...but I have written my first one and it is running as expected.
What I am currently doing is writing to the log with sh name-of-script.sh >> /cron.log 2>&1. However this writes everything out. It was great for debugging but now I don't need that.
I now only want to see the start date and time along with the end date and time
I would still like to write to cron.log but just the dates as mentioned above But I can't seem to figure out how to do that. Can someone point me in the right direction to do this...either from within the script or similar to what I've done above?
A simple approach would be to add something like:
echo `date`: Myscript starts
to the top of your script and
echo `date`: Myscript ends
to the bottom and
echo `date`: Myscript exited because ...
wherever it exits with an error.
The backticks around date (not normal quotes) cause the output of the date command to be interpolated into the echo statement.
You could wrap this in functions and so forth to make it neater, or use date -u to print in UTC, but this should get you going.
You ask in the comments how you would avoid the rest of the output appearing.
One option would be to redirect the output and error of everything else in the script to /dev/null, by adding '>/dev/null 2>&1' to every line that output something, or otherwise silence them. EG
if fgrep myuser /etc/password ; then
dosomething
fi
could be written:
if fgrep myuser /etc/password >/dev/null 2>&1 ; then
dosomething
fi
though
if fgrep -q myuser /etc/password ; then
dosomething
fi
is more efficient in this case.
Another option would be to put the date wrapper in the crontab entry. Something like:
0 * * * * sh -c 'echo `date`: myscript starting ; /path/to/myscript >/dev/null 2>&1; echo `date`: myscript finished'
Lastly, you could use a subshell. Put the body of your script into a function, and then call that in a subshell with output redirected.
#!/bin/bash
do_it ()
{
... your script here ...
}
echo `date`: myscript starting
( do_it ) >/dev/null 2>&1
echo `date`: myscript finished
Try the following:
TMP=$(date); name-of-scipt.sh; echo "$TMP-$(date)"
or with formatted date
TMP=$(date +%Y%m%d.%H%M%S); name-of-scipt.sh; echo "$TMP-$(date +%Y%m%d.%H%M%S)"

bashscript is not getting invoked in current shell through crontab

everyting working as root user in linuxmint. my bashscript works perfectly as a single
script and generating logfile as well but it is not getting invoked in my current shell through crontab.
path of bashscript: /root/Documents/mybashscript.sh
crontab line:
0.9 * * * * root /root/Documents/mybashscript.sh > /root/Documents/crontab.log
mybashscript.sh inside commands are as below:
#!/bin/bash
source /root/.profile
echo -n "Please enter your name: "
read name
TIME=‘date +%H‘
case $TIME in
0[6-9] | 1[01] ) echo -n "Good morning";;
12 ) echo -n "Good Noon";;
1[2-6] ) echo -n "Good Afternoon";;
1[7-9] ) echo -n "Good Evening";;
*) echo -n "Good Night";;
esac
echo " $name, Nice to meet you!"
Can anybody tell me how to trouble shoot.
First things first, I don't believe 0.9 is a valid minute indicator. I think you probably want 0-9 (depending on when you want it to run of course).
Secondly, you appear to have an extraneous root in your cron entry (the first one following the final *).
Thirdly, cron jobs don't really work that well for interactive input like:
read name
so I'm not sure what you're trying to achieve there.

Resources