Mistake in shell function to count even numbers
For an assignment I have to write a function that prints the number of even numbers when provided with a sequence of numbers.
I used the piece of code I used for a previous assignment (to print 1
when a number was even and 0
when the number was odd)
My problem now is that my function keeps printing 0
.
What am I doing wrong?
Here's my script:
#!/usr/bin/bash
# File: nevens.sh
# Write a function called nevens which prints the number of even numbers when provided with a sequence of numbers.
# Check: input nevens 42 6 7 9 33 = output 2
function nevens {
local sum=0
for element in $@
do
let evencheck=$(( $# % 2 ))
if [[ $evencheck -eq 0 ]]
then
let sum=$sum+1
fi
done
echo $sum
}
Solution 1:
You just forgot to replace $#
with ($
)element
in the for
loop:
function nevens {
local sum=0
for element in $@; do
let evencheck=$(( element % 2 ))
if [[ $evencheck -eq 0 ]]; then
let sum=sum+1
fi
done
echo $sum
}
Now to test the function:
$ nevens 42 6 7 9 33
2
$ nevens 42 6 7 9 33 22
3
$ nevens {1..10..2} # 1 to 10 step 2 → odd numbers only
0
$ nevens {2..10..2} # 2 to 10 step 2 → five even numbers
5
Solution 2:
@dessert has found the core problem, I'll give some code review:
- The shebang: There is no
/usr/bin/bash
in Ubuntu. It's/bin/bash
. -
It's good that you have declared
sum
local
, and avoided polluting the variable namespace outside the function. Additionally, you can declare it an integer variable using the-i
option:local -i sum=0
-
Always quote your variables (and parameters)! It's not necessary in this script, but a very good habit to get into:
for element in "$@" do
That said, you can omit the
in "$@"
here:for element do
When
in <something>
is not given, thefor
loop implicitly loops over the arguments. This can avoid mistakes like forgetting the quotes. -
There's no need to calculate and then check the result. You can directly do the calculation in the
if
:if (( (element % 2) == 0 )) then ((sum = sum + 1)) fi
(( ... ))
is the arithmetic context. It's more useful than[[ ... ]]
for performing arithmetic checks, and additionally you can omit the$
before variables (which makes it easier to read, IMHO). -
If you moved the even-checking part into a separate function, it might improve readability and reusability:
function evencheck { return $(( $1 % 2 )) } function nevens { local -i sum=0 for element do # `if` implicitly checks that the returned value/exit status is 0 if evencheck "$element" then (( sum++ )) fi done echo "$sum" }
Solution 3:
I'm not sure if you're open to other solutions. Also I don't know if you can use external utilities, or if you're purely limited to bash builtins. If you can use grep
, for example, your function could be a whole lot simpler:
function nevens {
printf "%s\n" "$@" | grep -c '[02468]$'
}
This puts each input integer on its own line, and then uses grep
to count the lines that end in an even digit.
Update - @PeterCordes pointed out that we can even do this without grep - just pure bash, so long as the input list contains just well formed integers (with no decimal points):
function nevens{
evens=( ${@/%*[13579]/} )
echo "${#evens[@]}"
}
This works by creating a list called evens
by filtering out all the odds, then returning the length of that list.