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

For discussions about programming, programming questions/advice, and projects that don't really have anything to do with Puppy.
Message
Author
musher0
Posts: 14629
Joined: Mon 05 Jan 2009, 00:54
Location: Gatineau (Qc), Canada

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

#1 Post 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.
Attachments
OddOrEven.sh.zip
(634 Bytes) Downloaded 92 times
musher0
~~~~~~~~~~
"You want it darker? We kill the flame." (L. Cohen)

User avatar
MochiMoppel
Posts: 2084
Joined: Wed 26 Jan 2011, 09:06
Location: Japan

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

#2 Post by MochiMoppel »

musher0 wrote:

Code: Select all

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

Code: Select all

${N: -1}
Mind the space!

musher0
Posts: 14629
Joined: Mon 05 Jan 2009, 00:54
Location: Gatineau (Qc), Canada

#3 Post by musher0 »

Hello MochiMoppei.

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

Bye for now.
musher0
~~~~~~~~~~
"You want it darker? We kill the flame." (L. Cohen)

User avatar
MochiMoppel
Posts: 2084
Joined: Wed 26 Jan 2011, 09:06
Location: Japan

#4 Post by MochiMoppel »

modulo haters and ash lovers could use

Code: Select all

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

musher0
Posts: 14629
Joined: Mon 05 Jan 2009, 00:54
Location: Gatineau (Qc), Canada

#5 Post 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! ;)
musher0
~~~~~~~~~~
"You want it darker? We kill the flame." (L. Cohen)

User avatar
MochiMoppel
Posts: 2084
Joined: Wed 26 Jan 2011, 09:06
Location: Japan

#6 Post 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
Last edited by MochiMoppel on Thu 22 Feb 2018, 13:39, edited 1 time in total.

User avatar
puppy_apprentice
Posts: 299
Joined: Tue 07 Feb 2012, 20:32

#7 Post 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

User avatar
technosaurus
Posts: 4853
Joined: Mon 19 May 2008, 01:24
Location: Blue Springs, MO
Contact:

#8 Post 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 ]]
Check out my [url=https://github.com/technosaurus]github repositories[/url]. I may eventually get around to updating my [url=http://bashismal.blogspot.com]blogspot[/url].

musher0
Posts: 14629
Joined: Mon 05 Jan 2009, 00:54
Location: Gatineau (Qc), Canada

#9 Post 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.
musher0
~~~~~~~~~~
"You want it darker? We kill the flame." (L. Cohen)

musher0
Posts: 14629
Joined: Mon 05 Jan 2009, 00:54
Location: Gatineau (Qc), Canada

#10 Post 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.
musher0
~~~~~~~~~~
"You want it darker? We kill the flame." (L. Cohen)

User avatar
puppy_apprentice
Posts: 299
Joined: Tue 07 Feb 2012, 20:32

#11 Post 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).
Attachments
evenorodd.jpg
In my bash it works
(25.6 KiB) Downloaded 169 times

User avatar
technosaurus
Posts: 4853
Joined: Mon 19 May 2008, 01:24
Location: Blue Springs, MO
Contact:

#12 Post 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
Check out my [url=https://github.com/technosaurus]github repositories[/url]. I may eventually get around to updating my [url=http://bashismal.blogspot.com]blogspot[/url].

User avatar
MochiMoppel
Posts: 2084
Joined: Wed 26 Jan 2011, 09:06
Location: Japan

#13 Post 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
.
Last edited by MochiMoppel on Fri 23 Feb 2018, 02:43, edited 1 time in total.

User avatar
Flash
Official Dog Handler
Posts: 13071
Joined: Wed 04 May 2005, 16:04
Location: Arizona USA

#14 Post by Flash »

Not to hijack the thread, I'd like to throw in that a prime number cannot be an even number.

User avatar
technosaurus
Posts: 4853
Joined: Mon 19 May 2008, 01:24
Location: Blue Springs, MO
Contact:

#15 Post 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
Check out my [url=https://github.com/technosaurus]github repositories[/url]. I may eventually get around to updating my [url=http://bashismal.blogspot.com]blogspot[/url].

User avatar
technosaurus
Posts: 4853
Joined: Mon 19 May 2008, 01:24
Location: Blue Springs, MO
Contact:

#16 Post 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} ]]
Check out my [url=https://github.com/technosaurus]github repositories[/url]. I may eventually get around to updating my [url=http://bashismal.blogspot.com]blogspot[/url].

musher0
Posts: 14629
Joined: Mon 05 Jan 2009, 00:54
Location: Gatineau (Qc), Canada

#17 Post 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 &" }
	}
}
Attachments
pekwm-submenu-1-for-defaults.jpg
(94.67 KiB) Downloaded 289 times
pekwm-submenu-2-for-defaults.png
The overlap is intentional, between the first entry of this one and the last entry of the first one. (&quot;Gest. de fich.&quot; is abbreviated French for &quot;filemanager&quot;.)
(55.32 KiB) Downloaded 280 times
Last edited by musher0 on Fri 23 Feb 2018, 04:57, edited 3 times in total.
musher0
~~~~~~~~~~
"You want it darker? We kill the flame." (L. Cohen)

User avatar
MochiMoppel
Posts: 2084
Joined: Wed 26 Jan 2011, 09:06
Location: Japan

#18 Post 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.

musher0
Posts: 14629
Joined: Mon 05 Jan 2009, 00:54
Location: Gatineau (Qc), Canada

#19 Post 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.
musher0
~~~~~~~~~~
"You want it darker? We kill the flame." (L. Cohen)

User avatar
MochiMoppel
Posts: 2084
Joined: Wed 26 Jan 2011, 09:06
Location: Japan

#20 Post 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.

Post Reply