Cyclic cron tasks - cron

I need to set up a weekly email that cycles between 4 variations, eg.
variation a
then b
then c
then d
then back to a
etc
each week.
I'd like to do this using 4 cron tasks (each of which sends an email every 4 weeks), but I'm having trouble staggering them so that each task starts on a different week, rather than having them all send their emails on the first week then having nothing for the next 4 weeks.
My crontab looks like this at the moment:
0 8 * * 1/4 echo "Variation A" | mail -s "Test email" admin#mydomain.com
0 8 * * 1/4 echo "Variation B" | mail -s "Test email" admin#mydomain.com
0 8 * * 1/4 echo "Variation C" | mail -s "Test email" admin#mydomain.com
0 8 * * 1/4 echo "Variation D" | mail -s "Test email" admin#mydomain.com
I feel a bit like if I could control when each one sends the email for the first time then I could get it working right?
Can anyone see a better way of doing this? Preferably with a minimal of scripting (I'd like to keep all of the details within the crontab)

This is not something you can do with cron alone. You will have to use some sort of script to do this, storing the last variation you sent, and deciding which to send next based on that info. The cron entry would look something like:
0 8 * * 0 somescript.sh
This will execute the script at 8AM every Sunday. The script itself will have to decide which message to send.
You could use something like this:
#!/bin/sh
STATEFILE='emailstate'
VARIATION=`cat emailstate 2>/dev/null`
case "$VARIATION" in
'4' )
# send variation 4
echo 'Sending variation 4'
VARIATION='1'
;;
'3' )
# send variation 3
echo 'Sending variation 3'
VARIATION='4'
;;
'2' )
# send variation 2
echo 'Sending variation 2'
VARIATION='3'
;;
* )
# send variation 1
echo 'Sending variation 1'
VARIATION='2'
;;
esac
echo $VARIATION > $STATEFILE

Related

get full current crontab line, and create unique result email headers (from, subject, etc) per line

I have a giant crontab with many scripts on a linux machine. I need to be able to a) change the subject and/or from of cronjob result emails, because the default is unreadably long. b) Do so via a centralized solution. c) Only require minimal changes to the crontab itself.
For example this crontab line:
0 */3 * * * /path/to/script1 | /path/to/script2 | /path/to/script3
Creates this email subject:
Cron <cronuser#myserver> /path/to/script1 | /path/to/script2 | /path/to/script3
Which in my inbox gets cut off somewhere in the path to script1. (And many lines in the crontab are significantly longer.)
Options I've tried:
Piping to mail and setting subject etc per line (The -E preserves cron's default only-send-on-output behavior):
0 */3 * * * /path/to/script1 | /path/to/script2 | /path/to/script3 2>&1 | mail -E -s "test subject" -S from="Cron Script2 <cronuser#myserver.com>" recipient#myserver.com
This "works", but I want to centralize my changes in one place, and minimize how much I add to each cron line for readability
Using shell : (noop) command, which shows up first in the subject (note that the space after the : is important!):
0 */3 * * * : Descriptive Words; /path/to/script1 | /path/to/script2 | /path/to/script3
Unfortunately there's still too much unnecessary that cron puts before "Descriptive Words" on the subject line, so this is still unusable.
What I want to create:
Something generic, like this:
0 */3 * * * /path/to/script1 | /path/to/script2 | /path/to/script3 2>&1 | coolmailer.pl
coolmailer.pl would build the subject line by getting the commands on that cron line, strip out the paths and arguments, and email me this (optionally only if there's a fail in any of the scripts):
SUBJECT: script1 | script2 | script3
FROM: Cron Script3<noreply#myserver.com>
(actual results of the command /path/to/script1 | /path/to/script2 | /path/to/script3)
As a bonus, I'd also love to say whether any of the previous commands (script1 or script2) failed on the subject line.
This has turned out to be... way more complicated than I expected.
Challenges:
Find a way for a pipeline member (coolmailer) to know the other
members of the pipeline.
There's an ingenious method for 1 here using lsof how-do-you-determine-the-actual-command-that-is-piping-into-you but it also sometimes finds commands started by the scripts in my pipeline (ie if script1 forks processes or does system calls, those show up too any time they take long enough to complete.) Ditto for the method using process groups at the same link.
Find a way for a pipeline member (coolmailer) to know the results of
other members of the pipeline. (I realize this may not be possible at all, but the lsof hack gives me hope.)
Any better way? Does the fact that I'm running from cron buy me anything? Part of me wants to combine the lsof strategy with grepping through crontab -l results, but that just seems too kludgy and prone to errors.
Caveats:
I can have changes made to my account, but I can't make changes that
would effect all users. I.e. if there's a way to change cron's
mailing format server-wide, that doesn't help.
I can't realistically update every script called to handle emailing its own results, even if that's probably the "right" way.
I know about the mail -s -E -S options, but would prefer to have a single place to change things. Also, I really want to find a way to get the pipeline.
Language used now for "coolmailer" is Perl, but I'll try anything
My first attempt:
(which works, except it often also shows other commands started inside my scripts, which means it doesn't work)
#!/usr/bin/perl -w
my $pgid=`ps -o pgid= -p $$`;
my $lsofout = `/usr/sbin/lsof -g $pgid`;
my #otherpids = `echo "$lsofout" | awk '\$5 == "1w" { print \$2 }'`;
my #longcmds;
my #shortcmds;
foreach my $pid (#otherpids) {
chomp($pid);
if (my $cmd = `ps -o cmd= -p $pid 2>/dev/null`) {
chomp($cmd);
push #longcmds, $cmd;
next;
}
}
my $cmdline = join (' | ',#longcmds);
foreach my $cmd (#longcmds) {
$cmd =~ s/(\/\S+\/)(\S+)/$2/g;
push #shortcmds, $cmd;
}
my $subj = join('|',#shortcmds);
print "SUBJ:$subj\n";
print "CMDLINE: $cmdline\n";
# and now do some mail stuff
And final version, based on suggestion by Jhnc
#!/usr/bin/perl -w
# cronmgr.pl -- understand cron emails for once
# usage: 0 */3 * * * cronmgr.pl cd blah\; /path/to/script1 \| /path/to/script2 \| /path/to/script3
# note that ; | & in any cronmgr.pl line must be backslashed to run!
use strict;
use IPC::Cmd qw[can_run run run_forked];
my $CMDLINE = join(' ',#ARGV);
my( $success, $error_message, $full_buf, $stdout_buf, $stderr_buf ) =
run( command => $CMDLINE, verbose => 0 ); # verbose = 0 means don't output normally, capture all output
my ($stdout, $stderr);
$stdout = join "", #$stdout_buf;
$stderr = join "", #$stderr_buf;
my $emailsubject;
if( $success ) {
if ($stdout eq '' && $stderr eq '') { # if there's no output, don't send any email!
exit;
}
} else {
print "CMD FAIL!\n$error_message\nSTDERR:\n$stderr";
$emailsubject = "FAIL:$error_message";
}
# etc etc
(Edited for clarity re goals and why options attempted so far aren't sufficient.)
determining the pipeline
If you always invoke coolmailer.pl with a unique argument then you can simply grep it from your list of cronjobs:
#!/usr/bin/perl -wT
$ENV{PATH} = '/sensible:/path';
my ($pipeline) = grep /\|\s+$0\s+$ARGV[0]/, `crontab -l`;
$pipeline ||= "oops";
# ... mung $pipeline ...
# ... do mail stuff ...
checking pipe failure
If you rewrite your cronjob entries from:
/path/to/script1 | /path/to/script2 | /path/to/script3 2>&1 | coolmailer.pl
to:
coolermailer /path/to/script1 \| /path/to/script2 \| /path/to/script3
then you could construct the pipeline manually and have control over pipe member status information. (This also gives you the pipeline directly, although you then have to construct it before it will run.)
For example, with a bash implementation, you might make use of eval and PIPESTATUS. With Perl, you might use results() from IPC::Run

Crontab setting - execute script every 55 minutes

I found a interesting thing during creation of my crontab setting.
I used this command:
crontab -e
and fill this line:
*/55 * * * * export DISPLAY=:0 && /home/user/Documents/script.sh $2>/dev/null
My idea was create scheduler, which start script.sh every 55 minutes.
But this script is execute in this times (for example):
08:55, 09:00, 09:05, 09:55, 10:00, 10:05, ...
and I don't know why.
Can someone explain me that?
Replace the script like this and it should work.
*/5 * * * * [ $(( $(date +%s) / 60 % 55 )) -eq 0 ] && export DISPLAY=:0 && /home/user/Documents/script.sh $2>/dev/null
minute-hour-day-month-year
* any value
, value list separator
- range of values
/ step values
Another option is a self-replicating 'at' job. Only advantage over cron is that it is less obvious, and also if you needed it to kick off not every X minutes, but X minutes after the last job completed. So your script will just contain a line to create a new 'at' job before it exits. Something like:
echo "/full/path/to/my/script > /root/myScript.at.log" | at now + X minutes
so every 5 minutes it will do this:
number of seconds elapsed since 1. 1. 1970 will divided by 60 = how many minutes
echo $(date +%s)
1476201056 ... second
echo $(( $(date +%s) / 60 ))
24603351 ... minutes
after that it will use modulo on count of minutes
When result of modulo is 0, it will send TRUE value.
And it is a typical logical AND
[ $((......)) -eq 0 ] && export DISPLAY.. && .../script.sh
Thank you.
It is really helpful :)

Running crontab only on one line in a file each time

I'm trying to configure crontab to execute at different times different lines of code inside a file. I basically have a bash script file that starts some java -jar. The problem is that each line should be executed at a different time. I can configure crontab to run the whole script at different times but no the lines to run. Now this is important that the bash file will stay only one file and not broken down to a few files.
Thanks!
One way of doing it (via command line arguments passed by cron)
some_script.sh:
if test $1 = 1 ; then
# echo "1 was entered"
java -jar some_file.jar
elif test $1 = 2 ; then
# echo "2 was entered"
java -jar another_file.jar
fi
crontab example:
* 1 * * * /bin/bash /home/username/some_script.sh 1
* 2 * * * /bin/bash /home/username/some_script.sh 2
Another approach (hour matching done in bash script)
some_script.sh:
hour=$(date +"%H");
if test $hour = 1 ; then
# echo "the hour is 1";
java -jar some_file.jar
elif test $hour = 2 ; then
# echo "the hour is 2";
java -jar another_file.jar
fi
crontab example:
* 1 * * * /bin/bash /home/username/some_script.sh
* 2 * * * /bin/bash /home/username/some_script.sh

bash + progress dialog bar in linux machine

The script run_tasks.bash read the text file
The script need to read line by line the text file and execute the scripts in the text file
One important remark about file.txt
Lines numbers in the txt file could be different
For example on the first running lines in txt file could be 12
On the second running lines in txt file could be for example 213 , and so on .....
./run_tasks.bash /tmp/file.txt
Example of text file - file.txt
1 /tmp/run.sh
2 /var/tmp/load_run.pl
3 /etc/RT/LM.pl
.
.
What I want to do is a process dialog progress that illustrate the number of the tasks in the txt file
For example if number of tasks ( lines ) in the txt file is 34
Then the dialog process will start from 0% to 100% according to the 34 tasks
Or
If the number of tasks (lines) in the txt are 321 then the dialog process will start from 0% to 100% reference to the 321 tasks
Another thing the dialog need to view each of the ruining script in the dialog
Please advice how to build the dialog code according to my requirements
Mean while I have the following dialog code but I not understand how to fit this code to the different tasks ( lines ) in the txt file
#!/bin/sh
#A gauge Box example with dialog
(
c=10
while [ $c -ne 110 ]
do
echo $c
echo "###"
echo "$c %"
echo "###"
((c+=10))
sleep 1
done
) |
dialog --title "A Test Gauge With dialog" --gauge "Please wait ...." 10 60 0
It's a matter of math ;) In items I've put the total number of items you'll be processing. You probably want to have something like items=$(wc -l file.txt) there. To convert the number of processed lines into a percentage, I do $(( $processed * 100 / $items)). Note the order, since we only have integers the usual processed/items*100 won't work.
#!/bin/bash
(
items=123
processed=0
while [ $processed -le $items ]; do
pct=$(( $processed * 100 / $items ))
echo "XXX"
echo "Processing item $processed"
echo "XXX"
echo "$pct"
processed=$((processed+1))
sleep 0.1
done
) | dialog --title "Gauge" --gauge "Wait please..." 10 60 0

Switch statement in csh

I am trying to make a switch statement to work in tcsh but I am not sure why it is not working. I am displaying a menu on the screen and if the option is selected it shows the price and then goes back to the top and repeats until the exit option is selected.
#!/bin/csh
clear
echo -n "Petes A Pizza "
echo -n " Menu "
echo -n " "
echo -n " Make a selection "
echo -n " "
echo -n " A. Speciality Pizza "
echo -n " B. Veggi Lovers Pizza "
echo -n " C. Meat Lovers Pizza "
echo -n " D. Hawaiian Pizza "
echo -n " E. Cheese Pizza "
echo -n " F. Exit "
set a = $<
switch ($a)
case [A] :
set A = ((7.99 + 0.07))
echo $A
sleep 5
goto top
case [B] : #they choose option 2
set B = ((8.99 * 0.07) + 8.99)
echo $B
sleep 5
goto top
case [C] : #they choose option 3
set C = ((6.99 * 0.07) + 6.99)
echo $C
sleep 5
goto top
case [D] : #they choose option 4
set D = ((8.49 * 0.07) + 8.49)
echo $D
sleep 5
goto top
case [E] : #they choose option 5
set E = ((3.99 * 0.07) + 3.99)
echo $E
sleep 5
case [F] :
exit 0
breaksw
endsw
end
Here are a few suggestions that should be enough to help you get it working.
Change #!/bin/csh to #!bin/csh -f. This tells the shell not to read your ~/.cshrc file, which saves time and can avoid confusion. (If you accidentally write code that depends on aliases you've defined in your .cshrc, for example, your script won't work for anyone else.)
If you must clear the screen, the clear command is the way to do it -- but why? If I want to clear my screen before running your script, I'll do it myself, thank you very much. If I have information on my screen that I don't want to lose, I'll be annoyed when your script decides to erase it for me.
Change all the echo -ns to just echo. The -n option tells echo to print its output without a trailing newline; your entire menu will be printed on one line.
The square brackets in your case labels are unnecessary. case A : means the same thing as case [A] :. Note that you're requiring the user to provide input in upper case, which may be inconvenient.
set A = ((7.99 + 0.07))
...
set B = ((8.99 * 0.07) + 8.99)
These are inconsistent. It looks like you're trying to compute a base price plus 7% sales tax. For case B, a simpler expression for that is 8.99 * 1.07.
csh doesn't recognize this (( ... )) syntax; I wonder where you got the idea that it does. csh can do arithmetic using the # command:
# x = 2 + 2
# x ++
but it only operates on integers. The bc command can do floating-point calculations. You could write something like:
set B = `echo 'scale=5; 1.07 * 8.99' | bc`
Or, more simply:
set B = `echo '1.07 * 8.99' | bc -l
but bc -l may give you more digits than you want. man bc for more information on the bc command, its syntax, and how it works. Remember that the values of csh variables are strings, not numbers.
(I'm not sure bc is the best tool for this job.)
Finally, csh is not the best language for writing scripts. I've been using it for more years than I care to admit, and I sometimes have to resort to trial and error to find out how a given piece of syntax will behave; the syntax is poorly defined in many cases, and the man page doesn't always clear things up.
Suggested reading: "Csh Programming Considered Harmful", by Tom Christiansen.

Resources