Bash always printing same value regardless of boolean value - bash

Related to SO.
fizzy.sh:
#!/usr/bin/env sh
div3() {
expr $1 % 3 = 0
}
div5() {
expr $1 % 5 = 0
}
fizzy() {
if [ $(div3 $1) ] && [ $(div5 $1) ]; then
expr "FizzBuzz"
elif [ $(div3 $1) ]; then
expr "Fizz"
elif [ $(div5 $1) ]; then
expr "Buzz"
else
expr "$1"
fi
}
echo $(fizzy 1)
echo $(fizzy 2)
echo $(fizzy 3)
Example:
$ ./fizzy.sh
FizzBuzz
FizzBuzz
FizzBuzz

expr $1 % 3 = 0 yields 1 or 0, depending on whether the result of $1 % 3 is zero or not, but if treats 0 as true, not false.
sh-3.2$ if [ 0 ]; then echo ok; fi
ok
So you'd need to compare the output of your function against 1. Something like this:
#!/usr/bin/env sh
div3() {
expr $1 % 3 = 0
}
div5() {
expr $1 % 5 = 0
}
fizzy() {
if [ $(div3 $1) -eq 1 ] && [ $(div5 $1) -eq 1 ]; then
expr "FizzBuzz"
elif [ $(div3 $1) -eq 1 ]; then
expr "Fizz"
elif [ $(div5 $1) -eq 1 ]; then
expr "Buzz"
else
expr "$1"
fi
}
for (( i = 1; i <= 15; i++ ))
do
echo $(fizzy $i)
done

Without the need for div3 or div5 functions.
fizzbuzz() { # eg: fizzbuzz 10
((($1%15==0))&& echo FizzBuzz)||
((($1%5==0))&& echo Buzz)||
((($1%3==0))&& echo Fizz)||
echo $1;
}
Or you could do it all at once
fizzbuzz() { # eg: fizzbuzz
for i in {1..100};
do
((($i%15==0))&& echo FizzBuzz)||
((($i%5==0))&& echo Buzz)||
((($i%3==0))&& echo Fizz)||
echo $i;
done;
}

If your shell is bash, you don't need to call out to expr:
div3() { (( $1 % 3 == 0 )); }
div5() { (( $1 % 5 == 0 )); }
fizzbuzz() {
if div3 $1 && div5 $1; then
echo FizzBuzz
elif div3 $1; then
echo Fizz
elif div5 $1; then
echo Buzz
else
echo
fi
}
for ((n=10; n<=15; n++)); do
printf "%d\t%s\n" $n $(fizzbuzz $n)
done

Related

Values of an array not comparing to numbers correctly

Im trying to get an array from grades.txt, and determine what letter grade it should be assigned.
I either get
hw4part2.sh: line 26: [: : integer expression expected
If i use -ge or
hw4part2.sh: line 26: [: : unary operator expected
If i use >=
Below is the code im trying to get working
mapfile -t scores < grades.txt
numOScores=0
numOA=0
numOB=0
numOC=0
numOD=0
numOF=0
DoneWScores=0
A=90
B=80
C=70
D=60
F=59
while [ $DoneWScores -eq 0 ]
do
numOScores=$((numOScores + 1))
if [ "${scores[$numOScores]}" -ge "$A" ]
then
echo "A"
elif [ "${scores[$numOScores]}" -ge "$B" ]
then
echo "B"
elif [ "${scores[$numOScores]}" -ge "$C" ]
then
echo "C"
elif [ "${scores[$numOScores]}" -ge "$D" ]
then
echo "D"
elif [ "${scores[$numOScores]}" -le "$F" ]
then
echo "F"
else
echo "Done/error"
DoneWScores=1
fi
done
If anyone knows what my problem is, that'd be greatly appreciated
Consider this:
#!/usr/bin/env bash
if (( ${BASH_VERSINFO[0]} < 4 )); then
echo "Bash version 4+ is required. This is $BASH_VERSION" >&2
exit 1
fi
letterGrade() {
if (( $1 >= 90 )); then echo A
elif (( $1 >= 80 )); then echo B
elif (( $1 >= 70 )); then echo C
elif (( $1 >= 60 )); then echo D
else echo F
fi
}
declare -A num
while read -r score; do
if [[ $score == +([[:digit:]]) ]]; then
grade=$(letterGrade "$score")
(( num[$grade]++ ))
echo "$grade"
else
printf "invalid score: %q\n" "$score"
fi
done < grades.txt
for grade in "${!num[#]}"; do
echo "$grade: ${num[$grade]}"
done | sort

bash function return in if statement not working

I'm trying to refactor this code:
if [ $(($1 % 4)) -eq 0 ] && [ $(($1 % 100)) -ne 0 ] || [ $(($1 % 400)) -eq 0 ] ; then
echo $T
else
echo $F
fi
into something like this:
if divisibleBy4 && notDivisibleBy100 || divisibleBy400; then
echo $T
else
echo $F
fi
note that
T="true"
F="false"
divisibleBy4 function looks like:
divisibleBy4() {
return [ $(($1 % 4)) -eq 0 ]
}
But I've tried several iterations including what I thought would definitely work.
divisibleBy4() {
if [ $(($1 % 4)) -eq 0 ]; then
return 1
else return 0
fi
}
Any idea how to properly fix the syntax so I can refactor these into functions and use them in my if statement?
When testing I'm seeing the error
syntax error: operand expected (error token is "% 4")
Another thing I tried is, but still doesn't seem to work:
INPUT=$1
divisibleBy4() {
if [ $(($INPUT % 4)) -eq 0 ]; then
return 1
else return 0
fi
}
notDivisibleBy100() {
if [ $(($INPUT % 100)) -ne 0]; then
return 1
else return 0
fi
}
divisibleBy400() {
if [ $(($INPUT % 400)) -eq 0 ]; then
return 1
else return 0
fi
}
if divisibleBy4 && notDivisibleBy100 || divisibleBy400; then
echo $T
else
echo $F
fi
or
INPUT=$1
divisibleBy4() {
return $((!($INPUT %4)))
}
notDivisibleBy100() {
return $(($INPUT %100))
}
divisibleBy400() {
return $((!($INPUT %400)))
}
(( divisibleBy4 && notDivisibleBy100 || divisibleBy400 )) && echo "true" || echo "false"
You want to detect a leap year!
A complete other solution using math mode directly:
a="$1"; (( !(a%4) && a%100 || !(a%400) )) && echo true || echo false
or as if-then-else
a="$1";
if (( !(a%4) && a%100 || !(a%400) )); then
echo true
else
echo false
The simplest, directest answer is to just create functions that consist only of the tests themselves:
INPUT=$1
divisibleBy4() {
[ $(($INPUT % 4)) -eq 0 ]
}
notDivisibleBy100() {
[ $(($INPUT % 100)) -ne 0 ]
}
divisibleBy400() {
[ $(($INPUT % 400)) -eq 0 ]
}
The reason this works is that a function without a return will implicitly return the status of the last command in the function; in these cases, that's the test command (note: [ is a command, even though it doesn't look like one), so the functions just return the result of the test directly.
I'd make at least one change to these, though: they all test the value of the shell variable INPUT; it's much better practice to actually pass the data that functions operate on as parameters. Thus, it'd be better to do something like this:
divisibleBy4() {
[ $(($1 % 4)) -eq 0 ]
}
if divisibleBy4 "$1" ...
Rather than this:
divisibleBy4() {
[ $(($INPUT % 4)) -eq 0 ]
}
INPUT=$1
if divisibleBy4 ...
Note that you can also bundle up the whole leap year check the same way:
isLeapYear() {
[ $(($1 % 4)) -eq 0 ] && [ $(($1 % 100)) -ne 0 ] || [ $(($1 % 400)) -eq 0 ]
}
if isLeapYear "$1"; then
Or use the simpler form #Wiimm suggested:
isLeapYear() {
(( !($1%4) && $1%100 || !($1%400) ))
}
Also, for the shell variables you do use, lower- or mixed-case is preferred, to avoid accidental conflicts with the many all-caps variable names that have special meanings or functions.
Every command sets a result code. If you want to force each calculation to happen in a separate function, you can say
divisibleBy4() {
$((!("$1" % 4)))
}
notDivisibleBy100() {
$(("$1" %100))
}
divisibleBy400() {
$((!("$1" %400)))
}
(divisibleBy4 "$1" &&
notDivisibleBy100 "$1" ||
divisibleBy400 "$1") &&
echo "true" || echo "false"
Breaking up your logic to functions on the subatomic level is not really helping legibility and maintainability, though. Perhaps if you want to make each part reasonably self-domumenting, use comments.
is_leap () {
# Divisible by 4?
(("$1" % 4 == 0)) || return
# Not divisible by 100?
(("$1" % 100 > 0)) && return
# Divisible by 400?
(("$1" % 400 == 0))
}
... Though the comments seem rather superfluous here.

AWK int comparison statements not working

I am using Hacker Rank challenges to teach myself BASH, and I'm in need of some advice.
I'm specifically trying to solve this challenge: Apple and Oranges by nabila_ahmed
I need to read in multiple lines of ints separated by spaces, on multiple lines. I decided to use awk to do this because it seems a lot more efficient in memory storage than using read. (I tried a couple of solutions using read and they timed out, because the test cases are really big.)
Example input:
7 11
5 15
3 2
-2 2 1
5 -6
This is my first attempt in bash and it timed out:
row=0
while read line || [[ -n $line ]]; do
if [ "$row" -eq 0 ]
then
column=0
for n in $line; do
if [ "$column" -eq 0 ]
then
housePos1=$n
elif [ "$column" -eq 1 ]
then
housePos2=$n
fi
((column++))
done
# Calculate house min and max
if [ "$housePos1" -gt "$housePos2" ]
then
minHousePos=$housePos2
maxHousePos=$housePos1
else
minHousePos=$housePos1
maxHousePos=$housePos2
fi
elif [ "$row" -eq 1 ]
then
column=0
for n in $line; do
if [ "$column" -eq 0 ]
then
appleTreePos=$n
elif [ "$column" -eq 1 ]
then
orangeTreePos=$n
fi
((column++))
done
elif [ "$row" -eq 3 ]
then
applesInHouse=0
for n in $line; do
# Calculate the apple's position
let applePos=$((appleTreePos + n))
# If the apple's position is within the houses position, count it
if [ "$applePos" -ge "$minHousePos" ] && [ "$applePos" -le "$maxHousePos" ]
then
((applesInHouse++))
fi
done
elif [ "$row" -eq 4 ]
then
orangesInHouse=0
for n in $line; do
# Calculate the apple's position
let orangePos=$((orangeTreePos + n))
# If the apple's position is within the houses position, count it
if [ "$orangePos" -ge "$minHousePos" ] && [ "$orangePos" -le "$maxHousePos" ]
then
((orangesInHouse++))
fi
done
fi
((row++))
done
echo "$applesInHouse"
echo "$orangesInHouse"
Here is my second attempt in bash, even more of the solutions timed out:
x=0;y=0;read -r s t;read -r a b;read -r m n;
for i in `seq 1 $m`; do
if [ "$i" -lt "$m" ]
then
read -d\ z
else
read -r z
fi
if [ "$((a+z))" -ge "$s" ] && \
[ "$((a+z))" -le "$t" ]
then
((x++))
fi
done
for i in `seq 1 $n`; do
if [ "$i" -lt "$n" ]
then
read -d\ z
else
read -r z
fi
if [ "$((b+z))" -ge "$s" ] && \
[ "$((b+z))" -le "$t" ]
then
((y++))
fi
done
echo $x; echo $y
Here's where I am at in debugging my solution using awk...
awk -v RS='[-]?[0-9]+' \
'{
if(word==$1) {
counter++
if(counter==1){
s=RT
}else if(counter==2){
t=RT
}else if(counter==3){
a=RT
}else if(counter==4){
b=RT
}else if(counter==5){
m=RT
}else if(counter==6){
n=RT
}else{
counter2++
if(counter2<=m){
print "Apples:"
print a+RT
print a+RT>=s
print a+RT<=t
applecount++
}
if(counter2>m && counter2<=m+n){
print "Oranges:"
print b+RT
print b+RT>=s
print b+RT<=t
orangecount++
}
}
}else{
counter=1
word=$1
}
}
END {
print "Total Counts:"
print applecount
print orangecount
}
'
Here is the output from that script when using the sample input
Apples:
3
0
0
Apples:
7
1
0 <-- This is the problem! (7 is less than or equal to 11)
Apples:
6
0
0
Oranges:
20
0
0
Oranges:
9
1
0 <-- This is also a problem! (9 is less than or equal to 11)
Total Counts:
3
2
As you can see, I'm getting some of the wrong comparisons...
ANSWER
(mostly courtesy of #glenn-jackman)
apples_oranges() {
local s t a b m n d
local -a apples oranges
local na=0 nb=0
{
read s t
read a b
read m n
read -a apples
read -a oranges
} < "$1"
for d in "${apples[#]}"; do
(( s <= a+d && a+d <= t )) && ((na++))
done
echo $na
for d in "${oranges[#]}"; do
(( s <= b+d && b+d <= t )) && ((nb++))
done
echo $nb
}
apples_oranges /dev/stdin
I'd do this with bash
apples_oranges() {
local s t a b m n d
local -a apples oranges
local na=0 nb=0
{
read s t
read a b
read m n # unused
read -a apples
read -a oranges
} < "$1"
for d in "${apples[#]}"; do
(( a+d >= s )) && ((na++))
done
echo $na
for d in "${oranges[#]}"; do
(( b-d <= t )) && ((nb++))
done
echo $nb
}
apples_oranges input.txt
this may get you started...
$ awk '
NR==1{split($0,house)}
NR==2{split($0,trees)}
NR==3{split($0,counts)}
NR==4{split($0,apples)}
NR==5{split($0,oranges)}
END{for(i in apples)
if(trees[1]+apples[i]>=house[1] && trees[1]+apples[i]<=house[2]) a++; print a}' file

Shell : logical ANDs and ORs instead of if-else

I was wondering why
i=10
if [ $i -lt 5 ]; then
echo "$i < 5"
elif [ $i -gt 5 ]; then
echo "$i > 5"
elif [ $i -eq 5 ]; then
echo "$i = 5"
fi
Outputs proper result:
10 > 5
Whereas
i=10
[ $i -lt 5 ] && {
echo "$i < 5"
} || [ $i -gt 5 ] && {
echo "$i > 5"
} || [ $i -eq 5 ] && {
echo "$i = 5"
}
behaves an unusual way:
10 > 5
10 = 5
In my opinion, as the interpreter seeks for 1s, it should work like this:
0 && {} || 1 && {} || 0 && {}
0 so the 0 && {} is definitely 0; skip {}
1 means that {} must be checked to define the value of whole 1 && {}
So that the result is 1, but the only {} is executed stays after 1.
However, this all does work as it should when I put ! { instead of {s.
i=10
[ $i -lt 5 ] && ! {
echo "$i < 5"
} || [ $i -gt 5 ] && ! {
echo "$i > 5"
} || [ $i -eq 5 ] && ! {
echo "$i = 5"
}
WHY?! I thought it seeks for 1s so since it finds a 0 in a && it doesn't look at other expressions in the chain!
The {...} does not make a difference, so what you have is equivalent to this:
i=10
[ $i -lt 5 ] &&
echo "$i < 5" ||
[ $i -gt 5 ] &&
echo "$i > 5" ||
[ $i -eq 5 ] &&
echo "$i = 5"
And the way this works is:
[ $i -lt 5 ]: This is false (returns failure), so it jumps to the next ||, which has [ $i -gt 5 ] following it.
[ $i -gt 5 ]: This is true (returns success), so it jumps to the next &&, which has echo "$i > 5" following it.
echo "$i > 5": This returns success, so it jumps to the next &&, which has echo "$i = 5" following it.
echo "$i = 5": This returns success, so it jumps to... wait no, there's a newline. We're done.
&& and || are called short-circuit operators.
EDIT: To stress the point further,
A && B || C
is NOT the same as
if A; then
B
else
C
fi
It's equivalent to
if A; then
if ! B; then
C
fi
else
C
fi
&& and || are evaluated from left to right. Your command is more or less equivalent to this:
(((( false && { echo 1; true; } ) || true ) && { echo 2; true; } ) || false ) && { echo 3; true; }
false && { echo 1; true; } doesn't print anything, and evaluates to false
false || true evaluates to true
true && { echo 2; true; } prints 2 and evaluates to true
true || false evaluates to true
true && { echo 3; true; } prints 3 and evaluates to true.
Mystery solved.

IF Condition for intervals made by real numbers in bash

This is a bash routine to compare two numbers with some defined intervals given by integers numbers:
#!/bin/bash
# The comparing function
function compareInterval {
t1=$1
t2=$2
shift 2
while (( "$2" )); do
if (( $t1 >= $1 && $t2 <= $2 )); then
# got match
return 0
fi
shift 2
done
return 1
}
# sample values
t_initial=2
t_final=4
# Invocation. Compares against 1-3, 3-5, 2-5
if compareInterval $t_initial $t_final 1 3 3 5 2 5; then
echo Got match
fi
If the intervals are given by real numbers, i.e., 1.234, how does the condition in the function change?
Here's a new version of the code:
#!/bin/bash
function compareInterval {
t1=$1
t2=$2
shift 2
while (( $(awk -v var="$2" 'BEGIN{ if (var=="") print 0; else print 1; }') )); do
var1=$(awk -v t1="$t1" -v t2="$1" 'BEGIN{ print (t1 >= t2) }')
var2=$(awk -v t3="$t2" -v t4="$2" 'BEGIN{ print (t3 <= t4) }')
if [[ "$var1" -eq "1" && "$var2" -eq "1" ]]; then
# got match
return 0
fi
shift 2
done
return 1
}
t_initial=4399.75148230007220954256
t_final=4399.75172111932808454256
if compareInterval $t_initial $t_final 4399.48390124308 4400.47652912846 3 5 2 5; then
echo Got match
fi
Just another pure bash solution:
#!/bin/bash
function compareInterval {
t1=$1 t2=$2
shift 2
while [[ $# -ge 2 ]]; do
is_ge "$t1" "$1" && is_le "$t2" "$2" && return 0 ## Got match.
shift 2
done
return 1
}
function is_ge {
local A1 A2 B1 B2
if [[ $1 == *.* ]]; then
A1=${1%%.*}
A2=${1##*.}
else
A1=$1
A2=0
fi
if [[ $2 == *.* ]]; then
B1=${2%%.*}
B2=${2##*.}
else
B1=$2
B2=0
fi
(( L = ${#A2} > ${#B2} ? ${#A2} : ${#B2} ))
A2=$A2'00000000000000000000'; A2=1${A2:0:L}
B2=$B2'00000000000000000000'; B2=1${B2:0:L}
(( A1 == B1 ? A2 >= B2 : A1 > B1 ))
}
function is_le {
local A1 A2 B1 B2
if [[ $1 == *.* ]]; then
A1=${1%%.*}
A2=${1##*.}
else
A1=$1
A2=0
fi
if [[ $2 == *.* ]]; then
B1=${2%%.*}
B2=${2##*.}
else
B1=$2
B2=0
fi
(( L = ${#A2} > ${#B2} ? ${#A2} : ${#B2} ))
A2=$A2'00000000000000000000'; A2=1${A2:0:L}
B2=$B2'00000000000000000000'; B2=1${B2:0:L}
(( A1 == B1 ? A2 <= B2 : A1 < B1 ))
}
t_initial=2.4
t_final=4.5
if compareInterval "$t_initial" "$t_final" 1 3 3 5 2 5; then
echo 'Got match.'
fi
Note: Of course sanity checks can be added but I find that not too necessary for now.

Resources