0

In a bash script (run every 30min), I want to do an action every even hour (and minutes 0), so I want to test the hour number if it's an even number or not. So I use this:

        HEURE=$(date +"%H")
        MINUTES=$(date +"%M")
        HEURE_PAIRE=""
        MINUTES_ZERO=""
        [[ $((HEURE % 2)) -eq 0 ]] && HEURE_PAIRE="OUI" || HEURE_PAIRE="NON"
        [[ $MINUTES -eq 0 ]] && MINUTES_ZERO="OUI" || MINUTES_ZERO="NON"
        if [[ "${HEURE_PAIRE}" == "OUI" ]] && [[ "MINUTES_ZERO" == "OUI" ]]; then
            # My code
        fi

But I get an error for this line (n°170 in my script) [[ $((HEURE % 2)) -eq 0 ]] && HEURE_PAIRE="OUI" || HEURE_PAIRE="NON" when it was 08:00, 08:30, 09:00, 09:30, but not for 10:00 or 10:30. See the error I get:

/volume4/docker/_Scripts-DOCKER/driver-pkgctl-r8152-restart-reload.sh: line 170: 08: value too great for base (error token is "07")
/volume4/docker/_Scripts-DOCKER/driver-pkgctl-r8152-restart-reload.sh: line 170: 09: value too great for base (error token is "09")

I expected to have it working like this: For 08:00, the 08%2 should return 0 as a result, proving hour is even. For 09:00, the 09%2 should return 1 as a result, proving hour isn't even. Not having an error

I assume it's the 0 before the 8 or 9... But I don't know how to fix this.

I found a workaround with this question by adding this:

HEURE=${HEURE#0}

It seems to be working.

My question: is there a better way to achieve what I want?

Thanks in advance.

MilesTEG
  • 3
  • 4

5 Answers5

0

Corrected script:

forcing base 10¹:

#!/bin/bash

heure=10#$(date +"%H") minutes=10#$(date +"%M")
if (( heure % 2 == 0 && minutes == 0 )); then
    # My code
fi

Another way using POSIX parameter expansions

#!/bin/bash

heure=$(date +"%H") minutes=$(date +"%M")
if (( ${heure#0} % 2 == 0 && ${minutes#0} == 0 )); then
    # My code
fi

another way using crontab

*/30 * * * * /path/to/script

Don't use UPPER case variables


((...)) and $((...)) are arithmetic commands, which returns an exit status of 0 if the expression is nonzero, or 1 if the expression is zero. Also used as a synonym for "let", if side effects (assignments) are needed. See http://mywiki.wooledge.org/ArithmeticExpression


¹ In arithmetic operations with bash, is there's a leading 0 like 08, it's treated as an octal number. To force base10, I use 10# prefix on hour and minutes


No need to quote in [[ ... ]] syntax, see http://mywiki.wooledge.org/BashFAQ/031 and http://mywiki.wooledge.org/BashGuide/TestsAndConditionals

Gilles Quénot
  • 173,512
  • 41
  • 224
  • 223
  • Thanks for your help. I learned something today :) May I submit the whole script to review? – MilesTEG Apr 16 '23 at 14:06
  • Don't know what kind of review do you mean – Gilles Quénot Apr 16 '23 at 14:09
  • @MilesTEG: See: https://codereview.stackexchange.com/ – Cyrus Apr 16 '23 at 14:13
  • Simplified for code review – Gilles Quénot Apr 16 '23 at 14:18
  • Just to check if it should be improved. But first of all, I need to lowercase my variables names ^^ Your correct script isn't working. The time format in the date commands isn't good. It should be: ```bash heure=10#$(date +"%H") minutes=10#$(date +"%M") ``` Because with the lowercase h and m, I get this (with faketime): ```bash faketime '08:00:00' ./test.sh dim. 16 avril 2023 08:00:00 CEST heure is : 10#avril minutes is : 10#04 ./test.sh: ligne 12: ((: 10#avril : valeur trop grande pour la base (le symbole erroné est « 10#avril ») ``` Line 12 is the one with the modulo test. – MilesTEG Apr 16 '23 at 14:20
  • It's a typo, sorry, I correct – Gilles Quénot Apr 16 '23 at 14:21
  • Another question linked to the previous one. I have this test : `if [[ "${gotify_always}" == "OUI" ]] || [ ${gotify_priority} -eq ${gotify_priority_error} ] || [ ${gotify_priority} -eq ${gotify_priority_fail} ] || { [ ${gotify_priority} -eq ${gotify_priority_success} ] && [[ "${heure_paire}" == "oui" ]] && [[ "${minutes_zero}" == "oui" ]]; }; then `. Is it correct ? PS : all other variables are string variables. – MilesTEG Apr 16 '23 at 14:39
  • Post another question. But mostly, can be simplified – Gilles Quénot Apr 16 '23 at 14:41
  • TIL that `#` in that context isn't the start of a comment. Not sure why it isn't but thanks! – Ed Morton Apr 16 '23 at 17:06
  • New question isn't possible right now, I'm on a question ban... And I can't improve my first one as it was badly asked and I don't really remember what I wanted to ask. PS : for the cron job possibility, I already put my script in the task scheduler of DSM (Synology NAS). So, I think, it's already in a cron job for every 30 minutes. My script is to be executed every 30min, but only every 2h 00min, it will send a gotify notification, or if there is an error or a fail. – MilesTEG Apr 16 '23 at 20:59
  • 2
    @EdMorton `#` has to be the first character of a word to signify a comment. See rules 7, 8 and 9: https://pubs.opengroup.org/onlinepubs/9699919799/utilities/V3_chap02.html#tag_18_03 – jhnc Apr 17 '23 at 23:27
  • 1
    @EdMorton I use a sort of 'cast' to avoid 'bash' to see e.x. 08 as octal. `10#08` is 'casted' in base 10 – Gilles Quénot Apr 17 '23 at 23:40
0

An alternative method without using arithmetic expressions:

#!/bin/bash

hm=$(date +%H%M)
if [[ $hm = ?[02468]00 ]]; then
    # do stuff
fi

or, in POSIX shell:

#!/bin/sh

hm=$(date +%H%M)
case $hm in
    ?[02468]00) do_stuff ;;
esac

or, with Bash arithmetic in a single operation:

#!/bin/bash

if (( 10#$(date +%H%M) % 200 == 0 )); then
        echo "It is time" 
fi
M. Nejat Aydin
  • 9,597
  • 1
  • 7
  • 17
  • Your solutions are similar to those of @jhnc . I like the one for bash. It seems efficient. I'm looking for the most efficient way to do what I want, and in a way I can understand :) – MilesTEG Apr 16 '23 at 21:06
  • @m-nejat-aydin : Third method is working. Can you explain, please? – MilesTEG Apr 17 '23 at 09:09
0

Setting the base is a good method for bash.

For POSIX shells that don't support that, there are other options.

Prefix with a non-zero digit:

heure=$(date +1%H)
...

Strip the leading zero:

heure=$(date +%H)
heure=${heure#0}
...

Compare textually:

heure=$(date +%H)
...
case $heure in ?[02468]) ... ;; esac
...
jhnc
  • 11,310
  • 1
  • 9
  • 26
  • Your second proposition is the one I used before @gilles-quénot give the first answer. Does your second proposition works for 13h, 14h... 23h ? I live in France where time is on 24 hours, not 12h AM/PM. – MilesTEG Apr 16 '23 at 21:03
  • Simple way to find out is to try it: eg. `h=02; echo ${h#0}; h=14; echo ${h#0}` – jhnc Apr 16 '23 at 23:23
  • it works. And what about the "Compare textually" method? – MilesTEG Apr 17 '23 at 09:05
  • what happens when you try it? – jhnc Apr 17 '23 at 13:45
  • I don’t take time to try it, not yet. But I always try to understand the command before using it. And here si don’t see what would happen with a 2 numbers hour like 13 ou 23… – MilesTEG Apr 17 '23 at 21:52
0

First this is bad:

HEURE=$(date +"%H")
MINUTES=$(date +"%M")

The time can switch e.g. from 10:59 to 11:00 in between. And then you get the wrong 10:00.

Use either

time=$( date +%H:%M )
HEURE="${time%:*}"
MINUTES="${time#*:}"

... or ...

time=$( date +%s )
HEURE=$( date -d@$time +%H )
MINUTES=$( date -d@$time +%M )

In both variants you get the time only once. In the second example you'll get the epoche-time and then calculates hour an minute from this time. Thew first example seems to be more efficient.

Then replace [[ $((HEURE % 2)) -eq 0 ]] by (( !( 10#$HEURE % 2 ))) or (( ( 10#$HEURE % 2 ) == 0 )). The prefix 10# forces the evaluation as decimal number.

Last note: In general, use lower case letters or mixed case for local var names.

Wiimm
  • 2,971
  • 1
  • 15
  • 25
  • That’s a good point. Time can change a little , yes. But my script is launched by DSM (Synology Nas) at 00:00 and every 30 minutes after that. S I assume that it fille be launched at 00:00:00 or some (milli)seconds after. And that’s not a problem in the tests made here . But you have a point here . It is preferable to get hour and minutes at the same time . I’ll implement that . Thanks. – MilesTEG Apr 17 '23 at 10:20
0

In numeric context, strings starting with a zero are considered octal. Therefore, 08 is in error.

However, I wouldn't do artithmetic to test for your condition. Instead, I would do something like

if [[ $(date +%HM) == ?[02468]00 ]]
then
  ....
fi
user1934428
  • 19,864
  • 7
  • 42
  • 87
  • That’s what @m-nejat-aydin answered before you. Is it like regex but for bash? Would you explain please how it work? – MilesTEG Apr 17 '23 at 21:56
  • It's not a regex, but a glob-pattern. In `[[ E1 == E2 ]]`, E2 is treated as glob pattern and E1 is matched against it. Globbing syntax is explained in the section _Pathname Expansion_ in the bash man-page.Be careful: If E2 is quoted, pattern matching is disabled. You can also use regex matching (bash uses POSIX extended regular expression). In this case the syntax is `[[ E1 =~ E2 ]]`. – user1934428 Apr 18 '23 at 05:51