Comparison operations in cron - linux

I'd like to configure cron to run script on the first day of every month if it is a working day. If the month starts on a weekend, I want the script to be run on the first following week day.
I tried do it this way
0 9 1-3 * * dom=$(date +\%d); dow=$(date +\%u); [[ $dow -lt 6 && (( $dom -eq 1 || $dow -eq 1 )) ]] && script.sh
This works in bash, but seems like cron can't execute comparisons.

By advice of Roadowl, adding
SHELL=/bin/bash
at the beginning of cron file made things work.

Related

Script in crontab to be executed only if is equal or exceeds a value

I currently have a script in crontab for rsync (and some other small stuff). Right now the script is executed every 5 minutes. I modified the script to look for a specific line from the rsync part (example from my machine, not the actual code):
#!/bin/bash
Number=`/usr/bin/rsync -n --stats -avz -e ssh 1/ root#127.0.0.1 | grep "Number of regular files transferred" | cut -d':' -f 2 | tr -d 040\054\012`
echo $Number
Let's say the number is 10. If the number is 10 or below I want the script executed through the crontab. But if the number is bigger I want to be executed ONLY manually.
Any ides?
Maybe you can use an argument to execute it manually, for example:
if [[ $Number -le 10 || $1 == true ]];then
echo "executing script..."
fi
This will execute if $Number is less or equal to 10 or if you execute it with true as the first positional argument, so if $Number is greater than 10 it won't execute in your crontab and you can execute your script manually with ./your_script true.

Script to run a command at 10 o'clock

I would like to know how I could run a script that will run a command on my PC if the time is equal to x. I have heard of the at command and some others but I'm trying to make mine a bit unique.
The problem is when I try my script, when my computer is on and I run it, it checks the hour but if I turn my PC on at x:36 or something, then my refresh will occur at an hour after x but with 36 minutes, advice please!
My script
time="$(date +%H)"
echo $time
if [ $time != "22" ]; then
sleep 1h
else
zenity --title="asdf" --text="asdf" --info
fi
You don't want to sleep 1 hour, you want to sleep until 2200. With GNU date, you can use
now=$(date +%s)
t=$(date +%s --date 2200)
sleep "$now"
zenity --title="asdf" --text="asdf" --info
If you want to run the command at 22:00 only, output and compare both hours and minutes:
time=$(date +%H:%M)
if [ $time != 22:00 ] ; then
sleep 58
...

Run CRON job every 5 minutes on OpenShift (Red Hat Cloud)

I'm trying to run this script every 5 minutes. It seems the only way to run CRON jobs on OpenShift is to use their CRON plugin. And the CRON plugin only allows for minutely, hourly, and daily scripts (by placing the script in the corresponding folder).
I am trying to run this script every 5 minutes:
#!/bin/bash
php /var/lib/openshift/53434c795973ca1ddc000668/app-root/runtime/repo/scheduled.php > /dev/null 2>&1
But right now it runs every minute (because it's placed in the minutely folder).
How can I re-write it so that it runs every 5 minutes?
Modify the script so it checks the current time, and bails out if it's not a multiple of 5 minutes.
Something like this:
#!/bin/bash
minute=$(date +%M)
if [[ $minute =~ [05]$ ]]; then
php ...
fi
The right operand of the =~ operator is a regular expression; the above matches if the current minute ends in 0 or 5. Several other approaches are possible:
if [[ $minute =~ .[05] ]]; then
(check for any character followed by a 0 or 5; $minute is always exactly 2 characters).
(User theshadowmonkey suggests in a comment:
if [ $(($minute % 5)) -eq 0 ]; then
which checks arithmetically whether $minute is a multiple of 5, but there's a problem with that. In the expression in a $(( ... )) expression, constants with leading zeros are treated as octal; if it's currently 8 or 9 minutes after the hour, the constant 08 or 09 is an error. You could work around this with sed, but it's probably not worthwhile given that there are other solutions.)
I will extend Keith Thompson answer:
His solution works perfectly for every 5 minutes but won't work for, let's say, every 13 minutes; if we use $minutes % 13 we get this schedule:
5:13
5:26
5:30
5:52
6:00 because 0%13 is 0
6:13
...
I'm sure you notice the issue. We can achieve any frequency if we count the minutes(, hours, days, or weeks) since Epoch:
#!/bin/bash
minutesSinceEpoch=$(($(date +'%s / 60')))
if [[ $(($minutesSinceEpoch % 13)) -eq 0 ]]; then
php [...]
fi
date(1) returns current date, we format it as seconds since Epoch (%s) and then we do basic maths:
# .---------------------- bash command substitution
# |.--------------------- bash arithmetic expansion
# || .------------------- bash command substitution
# || | .---------------- date command
# || | | .------------ FORMAT argument
# || | | | .----- formula to calculate minutes/hours/days/etc is included into the format string passed to date command
# || | | | |
# ** * * * *
$(($(date +'%s / 60')))
# * * ---------------
# | | |
# | | ·----------- date should result in something like "1438390397 / 60"
# | ·-------------------- it gets evaluated as an expression. (the maths)
# ·---------------------- and we can store it
And you may use this approach with hourly, daily, or monthly cron jobs on OpenShift:
#!/bin/bash
# We can get the
minutes=$(($(date +'%s / 60')))
hours=$(($(date +'%s / 60 / 60')))
days=$(($(date +'%s / 60 / 60 / 24')))
weeks=$(($(date +'%s / 60 / 60 / 24 / 7')))
# or even
moons=$(($(date +'%s / 60 / 60 / 24 / 656')))
# passed since Epoch and define a frequency
# let's say, every 7 hours
if [[ $(($hours % 7)) -ne 0 ]]; then
exit 0
fi
# and your actual script starts here
Notice that I used the -ne (not equal) operator to exit the script instead of using the -eq (equal) operator to wrap the script into the IF construction; I find it handy.
And remember to use the proper .openshift/cron/{minutely,hourly,daily,weekly,monthly}/ folder for your frequency.
You can make use of minutely cron job available. The jobs are run at a specified frequency and you can instrument your job to inspect the date and/or time when your job runs.
Here is an example for running a job every 5 minutes. Place the below code snippet in an executable file at path .openshift/cron/minutely/awesome_job and give proper permissions chmod +x awesome_job then add it to your application repository, commit and push.
The snippet
#!/bin/bash
if [ ! -f $OPENSHIFT_DATA_DIR/last_run ]; then
touch $OPENSHIFT_DATA_DIR/last_run
fi
if [[ $(find $OPENSHIFT_DATA_DIR/last_run -mmin +4) ]]; then #run every 5 mins
rm -f $OPENSHIFT_DATA_DIR/last_run
touch $OPENSHIFT_DATA_DIR/last_run
# The 'awesome_command(s)' that you want to run every 5 minutes
fi
Explanation:
This snippet actually creates an empty file named 'last_run' just to
note down the last run status and then starts your job.
The next minute, your 'awesome_job' will be executed and now it tries to find file 'last_run' modified in last 4 minutes. But your
file 'last_run' was created just a minute ago and hence the condition fails and exits. This continues.
At fifth minute, while finding (with -mmin +4), your 'last_run' will be shown and now, the file 'last_run' will be deleted and recreated and your awesome command(s) will be executed.
Source (Official Docs): https://developers.openshift.com/managing-your-applications/background-jobs.html#_execution_timing

Ubuntu Cronjob with rsync

Currently on workplaces server we run a daily backup, due to size limits we need it to be only each third day (or something like that). We use Rsync to do the backup. What I'm thinking to do is to just change the run time of the script, so insted of daily it will run every third day.
So I want to know wether this is possible? My Concerns are that the size wont shrink because the backup will still do a "3-days backup" insted of just one day. It's hard to explain so I'll show it by exampel.
What I want:
Day 1 - Run Backup
Day 2
Day 3
Day 4 - Run Backup
Day 5
What I fear will happen:
Day 1 - Run Backup
Day 2 - Backup applied from Day 4
Day 3 - Backup applied from Day 4
Day 4 - Run Backup
Day 5
the crontab job looks like this:
5 7 * * * ../rsyncsnapshot.sh daily 30
the script looks like this
if [ $# != 2 ]; then
echo "Usage: backup.sh interval_name count"
exit 1
fi
NAME=$1
COUNT=$2
TIMESTAMP=`date -u "+%Y-%m-%d %H:%M:%S%z"`
echo "*** Backup started $TIMESTAMP (interval $NAME, count $COUNT) ***"
echo "Deleting $DEST_DIR/$NAME.$((COUNT-1))"
ssh $DEST_HOST rm -rf $DEST_DIR/$NAME.$(($COUNT-1))
for i in `seq $(($COUNT-1)) -1 2`;
do
j=$(($i-1))
echo "Moving $DEST_DIR/$NAME.$j to $DEST_DIR/$NAME.$i"
ssh $DEST_HOST mv $DEST_DIR/$NAME.$j $DEST_DIR/$NAME.$i
done
echo "Copying $DEST_DIR/$NAME.0 to $DEST_DIR/$NAME.1"
ssh $DEST_HOST cp -al $DEST_DIR/$NAME.0 $DEST_DIR/$NAME.1
echo "Copying source ($SRC) to $DEST_HOST:$DEST_DIR/$NAME.0/"
rsync $RSYNC_ARGS $SRC $DEST_HOST:$DEST_DIR/${NAME}.0/
ssh $DEST_HOST touch $DEST_DIR/$NAME.0
TIMESTAMP=`date -u "+%Y-%m-%d %H:%M:%S%z"`
echo "*** Backup ended $TIMESTAMP ***"
echo "Quota as follows:"
ssh $DEST_HOST quota
To reduce the amount of space you use significantly, you'll need to reduce the number of copies you keep. This is the 2nd argument to the script. So if you run every 3 days, and want to keep a month of backups, change it to:
../rsyncsnapshot.sh daily 10

How can I tell if a file is older than 30 minutes from /bin/sh?

How do I write a script to determine if a file is older than 30 minutes in /bin/sh?
Unfortunately does not the stat command exist in the system. It is an old Unix system, http://en.wikipedia.org/wiki/Interactive_Unix
Perl is unfortunately not installed on the system and the customer does not want to install it, and nothing else either.
Here's one way using find.
if test "`find file -mmin +30`"
The find command must be quoted in case the file in question contains spaces or special characters.
The following gives you the file age in seconds:
echo $(( `date +%s` - `stat -L --format %Y $filename` ))
which means this should give a true/false value (1/0) for files older than 30 minutes:
echo $(( (`date +%s` - `stat -L --format %Y $filename`) > (30*60) ))
30*60 -- 60 seconds in a minute, don't precalculate, let the CPU do the work for you!
If you're writing a sh script, the most useful way is to use test with the already mentioned stat trick:
if [ `stat --format=%Y $file` -le $(( `date +%s` - 1800 )) ]; then
do stuff with your 30-minutes-old $file
fi
Note that [ is a symbolic link (or otherwise equivalent) to test; see man test, but keep in mind that test and [ are also bash builtins and thus can have slightly different behavior. (Also note the [[ bash compound command).
Ok, no stat and a crippled find. Here's your alternatives:
Compile the GNU coreutils to get a decent find (and a lot of other handy commands). You might already have it as gfind.
Maybe you can use date to get the file modification time if -r works?
(`date +%s` - `date -r $file +%s`) > (30*60)
Alternatively, use the -nt comparision to choose which file is newer, trouble is making a file with a mod time 30 minutes in the past. touch can usually do that, but all bets are off as to what's available.
touch -d '30 minutes ago' 30_minutes_ago
if [ your_file -ot 30_minutes_ago ]; then
...do stuff...
fi
And finally, see if Perl is available rather than struggling with who knows what versions of shell utilities.
use File::stat;
print "Yes" if (time - stat("yourfile")->mtime) > 60*30;
For those like myself, who don't like back ticks, based on answer by #slebetman:
echo $(( $(date +%s) - $(stat -L --format %Y $filename) > (30*60) ))
You can do this by comparing to a reference file that you've created with a timestamp of thirty minutes ago.
First create your comparison file by entering
touch -t YYYYMMDDhhmm.ss /tmp/thirty_minutes_ago
replacing the timestamp with the value thirty minutes ago. You could automate this step with a trivial one liner in Perl.
Then use find's newer operator to match files that are older by negating the search operator
find . \! -newer /tmp/thirty_minutes_ago -print
Here's my variation on find:
if [ `find cache/nodes.csv -mmin +10 | egrep '.*'` ]
Find always returns status code 0 unless it fails; however, egrep returns 1 is no match is found`. So this combination passes if that file is older than 10 minutes.
Try it:
touch /tmp/foo; sleep 61;
find /tmp/foo -mmin +1 | egrep '.*'; echo $?
find /tmp/foo -mmin +10 | egrep '.*'; echo $?
Should print 0 and then 1 after the file's path.
My function using this:
## Usage: if isFileOlderThanMinutes "$NODES_FILE_RAW" $NODES_INFO_EXPIRY; then ...
function isFileOlderThanMinutes {
if [ "" == "$1" ] ; then serr "isFileOlderThanMinutes() usage: isFileOlderThanMinutes <file> <minutes>"; exit; fi
if [ "" == "$2" ] ; then serr "isFileOlderThanMinutes() usage: isFileOlderThanMinutes <file> <minutes>"; exit; fi
## Does not exist -> "older"
if [ ! -f "$1" ] ; then return 0; fi
## The file older than $2 is found...
find "$1" -mmin +$2 | egrep '.*' > /dev/null 2>&1;
if [ $? == 0 ] ; then return 0; fi ## So it is older.
return 1; ## Else it not older.
}
Difference in seconds between current time and last modification time of myfile.txt:
echo $(($(date +%s)-$(stat -c "%Y" myfile.txt)))
you can also use %X or %Z with the command stat -c to get the difference between last access or last status change, check for 0 return!
%X time of last access, seconds since Epoch
%Y time of last data modification, seconds since Epoch
%Z time of last status change, seconds since Epoch
The test:
if [ $(($(date +%s)-$(stat -c "%Y" myfile.txt))) -lt 600 ] ; then echo younger than 600 sec ; else echo older than 600 sec ; fi
What do you mean by older than 30 minutes: modified more than 30 minutes ago, or created more than 30 minutes ago? Hopefully it's the former, as the answers so far are correct for that interpretation. In the latter case, you have problems since unix file systems do not track the creation time of a file. (The ctime file attribute records when the inode contents last changed, ie, something like chmod or chown happened).
If you really need to know if file was created more than 30 minutes ago, you'll either have to scan the relevant part of the file system repeatedly with something like find or use something platform-dependent like linux's inotify.
#!/usr/bin/ksh
## this script creates a new timer file every minute and renames all the previously created timer files and then executes whatever script you need which can now use the timer files to compare against with a find. The script is designed to always be running on the server. The first time the script is executed it will remove the timer files and it will take an hour to rebuild them (assuming you want 60 minutes of timer files)
set -x
# if the server is rebooted for any reason or this scripts stops we must rebuild the timer files from scratch
find /yourpath/timer -type f -exec rm {} \;
while [ 1 ]
do
COUNTER=60
COUNTER2=60
cd /yourpath/timer
while [ COUNTER -gt 1 ]
do
COUNTER2=`expr $COUNTER - 1`
echo COUNTER=$COUNTER
echo COUNTER2=$COUNTER2
if [ -f timer-minutes-$COUNTER2 ]
then
mv timer-minutes-$COUNTER2 timer-minutes-$COUNTER
COUNTER=`expr $COUNTER - 1`
else
touch timer-minutes-$COUNTER2
fi
done
touch timer-minutes-1
sleep 60
#this will check to see if the files have been fully updated after a server restart
COUNT=`find . ! -newer timer-minutes-30 -type f | wc -l | awk '{print $1}'`
if [ $COUNT -eq 1 ]
then
# execute whatever scripts at this point
fi
done
You can use the find command.
For example, to search for files in current dir that are older than 30 min:
find . -type f -mmin +30
You can read up about the find command HERE
if [[ "$(date --rfc-3339=ns -r /tmp/targetFile)" < "$(date --rfc-3339=ns --date '90 minutes ago')" ]] ; then echo "older"; fi

Resources