Need help with conditionals

I am new to bash and I don’t quite get how the conditionals work here. I tried running the following code to solve this exercise. My exact problem is with the value of flag. The code enters the final if block even when flag is true. Why is that happening?

main () {
    res=""
    flag="false"
    echo "$(( $1 % 3 == 0))"
    if [[ "$(( $1 % 3 == 0 ))" -eq 1 ]]
    then
	echo "here"
        res+="Pling"
        flag="true"
	echo $res
	echo $flag
    fi
    if [[ "$(( $1 % 5 == 0 ))" -eq 1 ]]
    then
        res+="Plang"
        flag="true"
    fi
    if [[ "$(( $1 % 7 == 0 ))" -eq 1 ]]
    then
        res+="Plong"
        flag="true"
    fi
    echo $flag
    if [[ $flag -eq "false" ]]
    then
        echo "why"
        res=$1
    fi
    echo $res
}

main "$@"

P.S. - General improvements to the code are also welcome :)

Are you sure about the -eq? As far as I know that’s for string integer comparisons.

But I’m not a heavy Bash user, so perhaps one of the experts knows more.

1 Like

TLDR; use == for string equality.

-eq is the arithmetic equality operator. In arithmetic context, bash allows you to refer to variables without the “parameter expansion syntax”, i.e. without the $.

The quoted conditional is equivalent to (( $true == $false ))

In arithmetic expressions, empty or unset variables get the value zero. Since both variables (named true and false) are unset, we have “does zero equal zero?”

At an interactive bash prompt, to see the list of conditional operators, do this sequence of commands:

help [
help test
help [[
1 Like

There is an arithmetic conditional expression ((...)) – see Conditional Constructs in the manual. Instead of wrapping an arithmetic expansion inside the string-oriented [[...]], use

if (( $1 % 3 == 0 )); then

As mentioned above, you can even put “bare” variables in there:

x=10

if [[ "$(( $x < 20 ))" -eq 1 ]]; then echo "smaller"; fi

# or, this is much tidier

if (( x < 20 )); then echo "smaller"; fi

Even works with arrays

arr=(11 22 33)
idx=1

if (( arr[idx] == 22 )); then echo "it's twenty-two"; fi
1 Like

One fun thing about flag=true and flag=falsetrue and false are bash builtin commands that exit with the expected status.

Note the syntax of the if command:

$ help if
if: if COMMANDS; then COMMANDS; [ elif COMMANDS; then COMMANDS; ]... [ else COMMANDS; ] fi
    Execute commands based on conditional.
    ...

It’s not required to use [ or [[ in an if statement, any command works. The branching happens based on the exit status of the command.

side note: this is a common thing you’ll see in shell scripts:

if grep -q "$pattern" "$file"; then ...

[ and [[ act just like any other command: they take arguments and have an exit status.

Since true and false are commands, this is valid:

if "$flag"; then
    echo "it's true"
else
    echo "it's false"
fi

Since echo is very simple (in terms of its exit status), we can also write

"$flag" && echo T || echo F
2 Likes

For flags, I prefer going C-style and not running commands.

flag=1

(( flag )) && echo T || echo F
1 Like

For some reason (( $1 % 3 == 0 )) doesn’t work for me - It simply won’t evaluate to a truthy value from what I understand. That’s why I’m using [[ "$(( $1 % 3 == 0 ))" -eq 1 ]].

If you’re using bash, it should work. Every command sets an exit value in the standard OS process model. The exit status of 0 is considered a success and all other values are failures. You can test it out manually in a terminal:

» a=1
» (( a == 1 )); echo $?
0
» (( a == 2 )); echo $?
1
» (( a == 1 )) && echo Yes || echo No
» (( a == 2 )) && echo Yes || echo No
No
1 Like

What is "$1" when you’re trying this?

1 Like

Thanks a lot for trying to help, Glenn. I managed to hack together some code that passes all testcases, but I still am a bit clueless about what’s happening with (( $1 % 3 == 0 )). I am being mentored by @IsaacG right now. I’ll summarize what went wrong with my code and my understanding of bash once I submit my code.

Coming to your question, I’m pretty sure "$1" is supposed to be the first argument, which is the input number.

Keep on working at it. I admire your persistance.

By the way, you are using Exercism exactly as it was designed to be used: attempt to solve the exercise, request mentoring, discuss challenges on the forum.

1 Like

So, to close this thread, this is what I actually got wrong - I was assuming that my code wasn’t working because something went wrong, and that the code wasn’t entering the if block.

main () {
    res=""
    if (( $1 % 3 == 0 ));then
        res+="Pling"
    fi
    if (( $1 % 5 == 0 ));then
        res+="Plang"
        flag=1
    fi
    if (( $1 % 7 == 0 ));then
        res+="Plong"
        flag=1
    fi
    if $res
    then
        res="$1"
    fi
    echo "$res"
}

main "$@"

Turns out that wasn’t the case when I looked at the error again. The actual error in the code was if $res. My experience with other programming languages led me to the wrong assumption that $res would evaluate to falsey value if it was an empty string and a truthy value if it wasn’t. What I didn’t realize was that if $res would treat the string as a command.

Thanks a lot for helping @glennj and @IsaacG :). I had a lot of fun figuring it out.

1 Like

Great news that this has hit home for you.

Are you open to receiving feedback about your code?

I already did receive feedback!
This is what it looks like now:

#!/usr/bin/env bash
main () {
    res=""
    (( $1 % 3 == 0 )) && res+="Pling"
    (( $1 % 5 == 0 )) && res+="Plang"
    (( $1 % 7 == 0 )) && res+="Plong"
    echo "${res:-$1}"
}
main "$@"

This is iteration no. 5 :grin: