1

I have a bash script that runs every Monday and Thursday from a crontab entry.

Inside that I have a condition to run one or another code depending on the code below:

if [ "$(LC_TIME=C date +%a)" == "Thu" ] && [ "$(date +%d)" -ge 15 ] && 
[ "$(date +%d)" -le 21 ] && [[ "$(LC_TIME=C date +%b)" == "Feb" || "$(date +%b)" == "Apr" || "$(date +%b)" == "Jun" || "$(date +%b)" == "Aug" || "$(date +%b)" == "Oct" || "$(date +%b)" == "Dec" ]]
then

    // condition a

else

    // condition b

fi

I'm not sure how best to test the above and am not overly confident in bash, I'm hoping 'condition a' will run every third Thursday of Feb, Apr etc, else 'condition b' will run.

Thanks in advance.

5
  • just add a parameter to the cron script to indicate A or B
    – suspectus
    Oct 18 '19 at 17:41
  • @suspectus, crontab doesn't provide for times like "third Thursday of the month", it would run on all the matching days of the month, and all Thursdays instead, see the note in the man page. That said, the months themselves could be listed in crontab.
    – ilkkachu
    Oct 18 '19 at 17:59
  • @ilkkachu thanks for the feedback. Wouldn't this be easily solved by having a Monday crontab entry and a Thursday one?
    – suspectus
    Oct 18 '19 at 18:15
  • 2
    @suspectus, mm, checking Monday vs. Thursday seems a different issue. What I meant is that you need to have at least one of the "is Thursday" and "is the third week of the month" checks inside the script, since * * 15-21 * 4 would on every Thursday and every day between the 15th and 21st. I don't know why it works like that, because it's not very useful, but it does, and there's really no way around it within crontab.
    – ilkkachu
    Oct 18 '19 at 18:30
  • Yeah, I've already been down that road :D Oct 21 '19 at 22:31
4

You only need to call date once. I would write this:

# ask date for the weekday, date and month: store into variables
read -r day date month < <( LC_TIME=C date "+%a %_d %b" )

# the 3rd Thu of the month has date between 15 and 21 inclusive
if  [[ $day == "Thu" ]] &&
    (( 15 <= date && date <= 21 )) &&            
    [[ ":Feb:Apr:Jun:Aug:Oct:Dec:" == *:"$month":* ]]
then
    echo "code a"
else
    echo "code b"
fi

This relies on the bash == operator being a pattern matching, not string equality, operator.

5
  • The pattern match is a good idea too. :)
    – ilkkachu
    Oct 19 '19 at 7:33
  • Thanks for the optimisations @glenn-jackman - looks good to me. I'll use this and see what happens :) Oct 21 '19 at 22:35
  • @glenn-jackman - Quick question about this, is there any reason why date is used without a $ in the conditional like $day and $month have? Nov 9 '19 at 14:20
  • 1
    bash let's you drop the $ in arithmetic expressions, ie within ((...)) . That allows the C-like for loop for ((i=0; i<n; i++)); do ... Nov 9 '19 at 14:28
  • Cool - thanks :) Nov 9 '19 at 14:35
2

That looks about right, you have the || placed within the [[ .. ]], so there shouldn't be a conflict between the && and ||.

Anyway, you can simplify that a bit. There's no need to run date separately for each test, just save the values to variables first, weekday=$(LC_TIME=C date +%a), etc.

Also, the list of months looks like it includes the ones with an even number (2, 4, 6, 8, 10 and 12), so you could just test that: [ "$((month % 2))" = 0 ] (% is the modulo operator).

Or in full, if I didn't make any mistakes:

weekday=$(LC_TIME=C date +%a)
dom=$(date +%-d)
month=$(date +%-m)

if [ "$weekday" = "Thu" ] && 
   [ "$dom" -ge 15 ] &&  [ "$dom" -le 21 ] && 
   [ "$((month % 2))" = 0 ]; then
        echo A
else
        echo B
fi

Using %-m (and %-d) to have date not print a leading zero. Bash would interpret it as meaning the number is in octal. An alternative would be removing it with month=${month#0}.

You could test that for arbitrary dates by using date -d in the assignments:

date=2019-04-17
weekday=$(LC_TIME=C date -d "$date" +%a)
dom=$(date -d "$date" +%-d)
month=$(date -d "$date" +%-m)
3
  • 2
    Be careful on the 8th or 9th day/month: month="08"; [ "$((month % 2))" = 0 ] && echo Ok => bash: 08: value too great for base (error token is "08") -- bash treats numbers starting with zero as octal. Either remove the leading zero with +%_m or use base 10 arithmetic (( 10#$month % 2 == 0 )) Oct 18 '19 at 19:32
  • @glennjackman, c**p, you're right. Thanks. I even saw that leading zero in date's output on some test, and still didn't remember it's an issue. (I hate that misfeature...) There's also %-m to remove all padding, %_m would pad with a space, which gets removed but isn't necessary either.
    – ilkkachu
    Oct 19 '19 at 7:30
  • Thanks for taking time to add an answer @ilkkachu - always good to have options :) Oct 21 '19 at 22:37

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.