Page 1 of 2

A simple way to detect an odd or even number in bash

Posted: Wed 21 Feb 2018, 23:06
by musher0
Hello all.

Here is a simple way to detect if a number is odd or even through a bash script.
Please pilfer and adapt to your needs! :)

There are a couple of other ways discussed, e.g. at the stackoverflow site, but
I saw this one nowhere before.

One explained trick consists of doing a modulo by two ( " % 2 ") of your number.
if the modulo is 1, you know you have an odd number; if "0", it is an even number.

Some devs are beautiful regex acrobats and use that. (I'm in awe. Really.)

You can browse through the following pages to get a taste of the problem -- and
of some answers.
https://www.ask.com/web?qsrc=1&o=0&l=di ... archTopBox

~~~~~~~~~~~~

I was getting nowhere with either in a script. I took a break.

After coffee, I thought: " Why not isolate the last digit of a number in a string
and compare it with the typical odd or even series?"

And the case function is the obvious to use for that. So here goes:

Code: Select all

#!/bin/bash
# Note: it has to be bash. See the $o variable below; 
# ash does not know what to do with $o-1 in the context.
#
# /opt/local/bin/OddOrEven.sh
# Or store in any "bin" directory in your $PATH.
#
# Usage: OddOrEven.sh $1
# OR
# by itself: OddOrEven.sh, and answer the prompt.
#
# Examples: OddOrEven.sh 111 # OddOrEven.sh 32
#
# # © Christian L'Écuyer, Gatineau (Qc), Canada, 2018/02/20. GPL3.
# (Alias musher0 [forum Puppy].) #
####
N=$1
if [ "$N" = "" ];then
     echo "Please type a number."
     read N
fi
o="${#N}" # ash does not know what to do with "${N:($o-1):1}" in the following.
case "${N:($o-1):1}" in 
     1|3|5|7|9) echo -e "\n\t$N is an odd number.\n" ;; # If odd number.
     0|2|4|6|8) echo -e "\n\t$N is an even number.\n" ;;
esac
IHTH someone. Feedback welcome. Bye for now.

Re: A simple way to detect an odd or even number in bash

Posted: Thu 22 Feb 2018, 01:46
by MochiMoppel
musher0 wrote:

Code: Select all

${N:($o-1):1}
Same as

Code: Select all

${N: -1}
Mind the space!

Posted: Thu 22 Feb 2018, 02:17
by musher0
Hello MochiMoppei.

You are a miser, saving a variable like that! :D

Bye for now.

Posted: Thu 22 Feb 2018, 04:41
by MochiMoppel
modulo haters and ash lovers could use

Code: Select all

o=$(echo $N | rev)
 case "${o::1}" in 

Posted: Thu 22 Feb 2018, 05:26
by musher0
MochiMoppel wrote:modulo haters and ash lovers could use

Code: Select all

o=$(echo $N | rev)
 case "${o::1}" in 
Very clever, but nah... That would be spoiling the user too much! ;)

Posted: Thu 22 Feb 2018, 10:17
by MochiMoppel
OK, that was cheating as rev does not qualify as "in bash".

This uses pure bash and works also in ash:

Code: Select all

case $N in 
  *[13579]) echo -e "\n\t$N is an odd number.\n" ;;
  *[02468]) echo -e "\n\t$N is an even number.\n" ;; 
esac
[Edit]: Forgot to mention that the result can only be called "odd" or "even" if $N is an integer

Posted: Thu 22 Feb 2018, 10:28
by puppy_apprentice
When i was learning Pascal in high school i was using this formula before i noticed that Pascal has odd function built-in.

Code: Select all

#!/bin/sh
check=`expr $1 / 2 \* 2`
if [ $check != $1 ]
then
  echo "$1 is odd"
else
  echo "$1 is even"
fi

Posted: Thu 22 Feb 2018, 10:34
by technosaurus
For any language with bitwise operations (like bash), just use &1; however, posix shell lacks bitwise ops, so you need to use the modulus operator %

Code: Select all

iseven() [[ $(($1%2)) = 0 ]]
isodd()  [[ $(($1%2)) = 1 ]]

Posted: Thu 22 Feb 2018, 15:03
by musher0
puppy_apprentice wrote:When i was learning Pascal in high school i was using this formula before i noticed that Pascal has odd function built-in.

Code: Select all

#!/bin/sh
check=`expr $1 / 2 \* 2`
if [ $check != $1 ]
then
  echo "$1 is odd"
else
  echo "$1 is even"
fi
You learned Pascal in high school? You must be very old?!?! :lol:

Euh... I thought that < expr > did not tolerate decimals. I think that if your $1 is
odd the < expr $1 \/2 > part of the calculation will exit with an error and you will
never get to the \* 2 part. I tried it in bash, and expr does not even give an error,
just a blank space, for any number.

Your trick works with bc, though, provided no "scale" if given:

Code: Select all

n=33;echo "($n/2)*2" | bc
32
And then your checks

Code: Select all

if [ $check != $n ]
then
  echo "$n is odd"
else
  echo "$n is even"
fi
can be done. If a "scale" is given:

Code: Select all

n=33;echo "scale=1;($n/2)*2" | bc
se get back the original number,
33.0
and the test is meaningless? Maybe not: 33.0 is not identical to 33. But that ceases
to be math, IMO, it becomes the logic of identity. In a string context, string
almost-A can never be string A.

It is indeed a good idea to check the input for an integer. However it is not needed
if the input number comes from a list with, say, wc -l < list.

BFN.

Posted: Thu 22 Feb 2018, 15:34
by musher0
technosaurus wrote:For any language with bitwise operations (like bash), just use &1; however, posix shell lacks bitwise ops, so you need to use the modulus operator %

Code: Select all

iseven() [[ $(($1%2)) = 0 ]]
isodd()  [[ $(($1%2)) = 1 ]]
Hi technosaurus.

Playing with your formula, like a kid with dough!

Code: Select all

n=33;[ $[$n%2] = 0 ] && echo even || echo odd
odd
BFN.

Posted: Thu 22 Feb 2018, 16:35
by puppy_apprentice
My formula works straight only for Normal Numbers. If you have decimal like 33.0 you have to first cut .0 eg.

Code: Select all

${1%.*}
Save as eg. evenorodd:

Code: Select all

#!/bin/sh
number=${1%.*}
check=`expr $number / 2 \* 2`
if [ $check != $number ]
then
  echo "$1 is odd"
else
  echo "$1 is even"
fi
Some mathematics:
0) number eg. 2 is even if:
1) you divide it by 2
2) round (1)
3) multiply (2) by 2
4) (3) is equal to (0).

expr rounds number automatically so you don't need to do (2).

Posted: Thu 22 Feb 2018, 23:42
by technosaurus
musher0 wrote:
technosaurus wrote:For any language with bitwise operations (like bash), just use &1; however, posix shell lacks bitwise ops, so you need to use the modulus operator %

Code: Select all

iseven() [[ $(($1%2)) = 0 ]]
isodd()  [[ $(($1%2)) = 1 ]]
Hi technosaurus.

Playing with your formula, like a kid with dough!

Code: Select all

n=33;[ $[$n%2] = 0 ] && echo even || echo odd
odd
BFN.
I actually screwed up and forgot to account for negative numbers, but here is a fix... If you just paste those at the top of your script you can use them like functions

Code: Select all

#!/bin/sh
iseven() [[ $(($1%2)) = 0 ]]
isodd() [[ ! $(($1%2)) = 0 ]]
even_odd(){
  iseven $1 && echo even || echo odd
}
even_odd 42
even_odd 7

Posted: Fri 23 Feb 2018, 01:54
by MochiMoppel
@musher0: Your original idea to use a case statement wasn't bad. While checking only the last digit doesn't tell you if you are dealing with an integer, you could easily adapt your case conditions to allow only integers and issue a warning if none of the conditions match. In the end such construct is not more complex than using bash arithmetic operators and doesn't require knowledge on how modulo or bash division work.

Using modulo and bash:

Code: Select all

[[ $N = *[^0-9-]* ]] && echo $N is not an integer && exit
(($N%2)) && echo $N is odd ||  echo $N is even
Note: The integer check avoids regex, but it doesn't catch oddly placed minus signs.

For a bullet proof check this should do:

Code: Select all

[[ $N =~ ^-?[0-9]+$ ]] || exec echo $N is not an integer
.

Posted: Fri 23 Feb 2018, 02:12
by Flash
Not to hijack the thread, I'd like to throw in that a prime number cannot be an even number.

Posted: Fri 23 Feb 2018, 02:44
by technosaurus
Flash wrote:Not to hijack the thread, I'd like to throw in that a prime number cannot be an even number.
2

Posted: Fri 23 Feb 2018, 03:09
by technosaurus
MochiMoppel wrote:For a bullet proof check this should do:

Code: Select all

[[ $N =~ ^-?[0-9]+$ ]] || exec echo $N is not an integer
for a non bashism version (as long as parameters don't contain spaces)

Code: Select all

isnumber() [[ $(($1+0)) = ${1} ]]

Posted: Fri 23 Feb 2018, 03:25
by musher0
MochiMoppel wrote:@musher0: Your original idea to use a case statement wasn't bad. While checking
only the last digit doesn't tell you if you are dealing with an integer, you could easily
adapt your case conditions to allow only integers and issue a warning if none of the
conditions match. In the end such construct is not more complex than using bash
arithmetic operators and doesn't require knowledge on how modulo or bash division
work.

Using modulo and bash:

Code: Select all

[[ $N = *[^0-9-]* ]] && echo $N is not an integer && exit
(($N%2)) && echo $N is odd ||  echo $N is even
Note: The integer check avoids regex, but it doesn't catch oddly placed minus signs.

For a bullet proof check this should do:

Code: Select all

[[ $N =~ ^-?[0-9]+$ ]] || exec echo $N is not an integer
.
Hello MochiMopei.

As I said above, I evolved this idea in the context of a list of files that I needed to
split in two "halves" (+/- one line) with head and tail. Now wc -l does not care if the
last line is only half full or a quarter full!! It was not even a text, just a list of files.
I did not need to check for decimals except if the total of lines is odd.

I am a man of the field, not a theorist. I respond to practical situations I encounter.
I was not expecting to have so many replies! But you guys with more knowledge
than me, please continue; this discussion has become most interesting.

Bye for now.

~~~~~~~~~~~~~~
Sorry to disappoint everyone (!), but this is the script where I originally encountered
this odd/even problem:

Code: Select all

#!/bin/sh
# ~/.pekwm/defaults-pekwm.sh
#
# Goal: create a pekwm sub-menu in two +/- equal parts for Puppy's' /usr/local/bin/default* files.
#
# © Christian L'Écuyer, Gatineau (Qc), Canada, 2018/02/18. GPL3
# (Alias musher0 [forum Puppy].) # Rév. : 2018/02/20
#################   # https://opensource.org/licenses/GPL-3.0
#    This program is free software: you can redistribute it and/or modify it under the
#    terms of the GNU General Public License as published by the Free Software
#    Foundation, either version 3 of the License, or  (at your option) any later version.
#         This program is distributed in the hope that it will be useful, but WITHOUT ANY
#    WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
#    A PARTICULAR PURPOSE. See the GNU General Public License for more details.
#         You should have received a copy of the GNU General Public License along with
#   this program. If not, see <http://www.gnu.org/licenses/>.
##########
#   Ce programme est libre : vous pouvez le redistribuer ou modifier selon les termes
#   de la Licence Publique Générale GNU publiée par la Free Software Foundation (v. 3
#   ou toute version ultérieure choisie par vous).
#       Ce programme est distribué dans l'espoir qu'il sera utile, mais SANS AUCUNE
#   GARANTIE, ni explicite ni implicite, y compris des garanties de commercialisation
#   ou d'adaptation à un but spécifique. Pour plus de détails, veuillez vous reporter
#   au texte officiel de cette licence à https://opensource.org/licenses/GPL-3.0, à
#   http://www.linux-france.org/article/these/gpl.html pour une traduction et, pour une
#   explication en français, à https://fr.wikipedia.org/wiki/Licence_publique_générale_GNU.
################
#### set -xe
cd ~/.pekwm
LC_ALL=C
ProgLine () { echo -e "\t\tEntry = "${line#*t}" { Actions = "Exec $line &" }" >> ~/.pekwm/menu.Defaults.tmp2; }

AppS=/usr/local/bin
cd $AppS
ls -Algo default* | awk '$1 !~ /lrw/ && $NF !~ /lts-|run|cdr|handler/ { print $NF }' > ~/.pekwm/default-progs

echo "}
# Ferme le sous-menu précédent." > ~/.pekwm/menu.Defaults

case "${LANG:0:2}" in
     fr)echo "Submenu = "Applis par défaut" {" >> ~/.pekwm/menu.Defaults ;;
     en|*)echo "Submenu = "Default Apps" {" >> ~/.pekwm/menu.Defaults ;;
esac

> ~/.pekwm/menu.Defaults.tmp2
while read line;do ProgLine;done < ~/.pekwm/default-progs
case "${LANG:0:2}" in
     fr) # Il est plus pratique de traduire avant de faire la division.
          RPLCT="replaceit --input=/root/.pekwm/menu.Defaults.tmp2"
$RPLCT archiver archiveur "}default" "{Entry";$RPLCT audiomixer mixeur "}default" "{Entry"
$RPLCT browser navigateur "}default" "{Entry";$RPLCT calendar calendrier "}default" "{Entry"
$RPLCT chat clavardage "}default" "{Entry";$RPLCT connect connexion "}default" "{Entry"
$RPLCT draw "ed. de dessin" "}default" "{Entry";$RPLCT email courriel "}default" "{Entry"
$RPLCT paint "ed. d'image" "}default" "{Entry";$RPLCT screenshot captures "}default" "{Entry"
$RPLCT spreadsheet chiffrier "}default" "{Entry";$RPLCT audioeditor "ed. audio" "}default" "{Entry"
$RPLCT audioplayer "lect. audio" "}default" "{Entry";$RPLCT cdplayer "lect. CD" "}default" "{Entry"
$RPLCT cdrecorder enregistreur "}default" "{Entry";$RPLCT chmviewer "vis. de chm" "}default" "{Entry"
$RPLCT filemanager "gest. de fich." "}default" "{Entry";$RPLCT htmleditor "ed. de html" "}default" "{Entry"
$RPLCT pdfviewer "vis. de pdf" "}default"" "{Entry";$RPLCT htmlviewer "vis. de html" "}default" "{Entry"
$RPLCT imageviewer "vis. d'image" "}default" "{Entry";$RPLCT mediaplayer "lect. media" "}default" "{Entry"
$RPLCT processmanager "gest. de proc." "}default" "{Entry"
$RPLCT texteditor "ed. de texte" "}default" "{Entry";$RPLCT textviewer "vis. de texte" "}default" "{Entry"
$RPLCT wordprocessor "tr. de texte" "}default" "{Entry"
          sort ~/.pekwm/menu.Defaults.tmp2 > ~/.pekwm/menu.Defaults.tmp ;; # Tri nécessaire
     en|*)
          mv ~/.pekwm/menu.Defaults.tmp2 ~/.pekwm/menu.Defaults.tmp ;;
esac
echo "Submenu = "1..." {" >> ~/.pekwm/menu.Defaults

# Vérifier si nlign est impair.
nlign="`cat ~/.pekwm/menu.Defaults.tmp | wc -l`"
case "${nlign: -1}" in # Si nombre impair, on aura un tuilage dans les entrées, # ;-) to MochiMoppei!
     1|3|5|7|9)nlign="`expr $nlign + 1`" ;;
esac
nlign="`expr $nlign \/ 2`" # mais on peut alors diviser sans reste.
#
head -n "$nlign"  /root/.pekwm/menu.Defaults.tmp >> ~/.pekwm/menu.Defaults
echo -e "\t}" >> ~/.pekwm/menu.Defaults
echo "Submenu = "2..." {" >> ~/.pekwm/menu.Defaults
tail -n "$nlign"  /root/.pekwm/menu.Defaults.tmp >> ~/.pekwm/menu.Defaults
echo -e "\t}\n}" >> ~/.pekwm/menu.Defaults

rm -f ~/.pekwm/menu.Defaults.tmp*
rm -f ~/.pekwm/default-progs

LC_ALL="" # set +xe
Scrots of the results are attached.

Also, these are the submenus (English version) that the above script creates,
which are read by the pekwm menu routine:

Code: Select all

}
# Ferme le sous-menu précédent.
Submenu = "Default Apps" {
Submenu = "1..." {
		Entry = "archiver" { Actions = "Exec defaultarchiver &" }
		Entry = "audioeditor" { Actions = "Exec defaultaudioeditor &" }
		Entry = "audiomixer" { Actions = "Exec defaultaudiomixer &" }
		Entry = "audioplayer" { Actions = "Exec defaultaudioplayer &" }
		Entry = "browser" { Actions = "Exec defaultbrowser &" }
		Entry = "calendar" { Actions = "Exec defaultcalendar &" }
		Entry = "cdplayer" { Actions = "Exec defaultcdplayer &" }
		Entry = "chat" { Actions = "Exec defaultchat &" }
		Entry = "chmviewer" { Actions = "Exec defaultchmviewer &" }
		Entry = "connect" { Actions = "Exec defaultconnect &" }
		Entry = "contact" { Actions = "Exec defaultcontact &" }
		Entry = "draw" { Actions = "Exec defaultdraw &" }
		Entry = "email" { Actions = "Exec defaultemail &" }
		Entry = "filemanager" { Actions = "Exec defaultfilemanager &" }
	}
Submenu = "2..." {
		Entry = "filemanager" { Actions = "Exec defaultfilemanager &" }
		Entry = "htmleditor" { Actions = "Exec defaulthtmleditor &" }
		Entry = "htmlviewer" { Actions = "Exec defaulthtmlviewer &" }
		Entry = "imageviewer" { Actions = "Exec defaultimageviewer &" }
		Entry = "mediaplayer" { Actions = "Exec defaultmediaplayer &" }
		Entry = "paint" { Actions = "Exec defaultpaint &" }
		Entry = "pdfviewer" { Actions = "Exec defaultpdfviewer &" }
		Entry = "processmanager" { Actions = "Exec defaultprocessmanager &" }
		Entry = "screenshot" { Actions = "Exec defaultscreenshot &" }
		Entry = "spreadsheet" { Actions = "Exec defaultspreadsheet &" }
		Entry = "terminal" { Actions = "Exec defaultterminal &" }
		Entry = "texteditor" { Actions = "Exec defaulttexteditor &" }
		Entry = "textviewer" { Actions = "Exec defaulttextviewer &" }
		Entry = "wordprocessor" { Actions = "Exec defaultwordprocessor &" }
	}
}

Posted: Fri 23 Feb 2018, 03:50
by MochiMoppel
musher0 wrote:I am a man of the field, not a theorist. I respond to practical situations I encounter.
I was trying to defend your solution, not dismiss it.

Posted: Fri 23 Feb 2018, 03:52
by musher0
MochiMoppel wrote:
musher0 wrote:I am a man of the field, not a theorist. I respond to practical situations I encounter.
I was trying to defend your solution, not dismiss it.
I know! And I thank you for it.

Posted: Fri 23 Feb 2018, 05:38
by MochiMoppel
technosaurus wrote:for a non bashism version (as long as parameters don't contain spaces)

Code: Select all

isnumber() [[ $(($1+0)) = ${1} ]]
AFAIK double brackets are not POSIX compliant though bash supports them also in its "more closely to the POSIX standard" mode.