Home > Software engineering >  Why does a BASH arithmetic expression error cause infinite while loops to break?
Why does a BASH arithmetic expression error cause infinite while loops to break?

Time:09-22

Why does performing a bad HEX conversion within a BASH arithmetic expression $((...)) cause all while loops to break?

Example:

#!/bin/bash

good_hex="ABCD"
bad_hex="EFGH"

while true;
do
    echo "Start 1"
    while true;
    do
        echo "Start 2"
        # Convert Hex to Decimal
        var1=$((16#$good_hex))
        echo "Good Hex: $var1"
        var2=$((16#$bad_hex))
        echo "Bad Hex: $var2"  # Won't be printed
        echo "End 2"           # Won't be printed
    done
    echo "Exit 2"              # Won't be printed
    echo "End 1"               # Won't be printed
done
echo "Exit 1"

Output:

chris@ubuntu:~$ ./hex_breaks.sh 
Start 1
Start 2
Good Hex: 43981
./hex_breaks.sh: line 15: 16#EFGH: value too great for base (error token is "16#EFGH")
Exit 1

After the bad hex conversion, nothing more inside either while loop is run. The next statement executed is "Exit 1" (outside of all while loops) and then the program terminates.

For comparison, using the "let" command instead of $((...)) causes the script to operate correctly and loop forever.

Example:

#!/bin/bash

good_hex="ABCD"
bad_hex="EFGH"

while true;
do
    echo "Start 1"
    while true;
    do
        echo "Start 2"
        # Convert Hex to Decimal
        let var1=16#$good_hex
        echo "Good Hex: $var1"
        let var2=16#$bad_hex
        echo "Bad Hex: $var2"  # Will be printed
        echo "End 2"           # Will be printed
    done
    echo "Exit 2"
    echo "End 1"
done
echo "Exit 1"

Output:

chris@ubuntu:~$ ./hex_works.sh 
Start 1
Start 2
Good Hex: 43981
./hex_works.sh: line 15: let: var2=16#EFGH: value too great for base (error token is "16#EFGH")
Bad Hex: 
End 2
Start 2
Good Hex: 43981
./hex_works.sh: line 15: let: var2=16#EFGH: value too great for base (error token is "16#EFGH")
Bad Hex: 
End 2
Start 2
Good Hex: 43981
./hex_works.sh: line 15: let: var2=16#EFGH: value too great for base (error token is "16#EFGH")
Bad Hex: 
End 2
Start 2
Good Hex: 43981
./hex_works.sh: line 15: let: var2=16#EFGH: value too great for base (error token is "16#EFGH")
Bad Hex: 
End 2

...

(Continues forever)

This script operates as expected and never breaks out of the while loop.

Some sources claim "let" and $((...)) are identical. My lint checker says I should use the $((...)) arithmetic compound instead of "let" because it's safer. However, breaking out of all conditional loops seems to be a completely unexpected side effect of a bad operation!

Any ideas what's going on??

CodePudding user response:

Some sources claim "let" and $((...)) are identical.

That's not true. let ... is a command, and $((...)) is an arithmetic expansion; the latter syntactic category is used to construct the former and doesn't make it known whether it succeeded or not to following commands unlike with commands, which set $? upon termination. For this reason, when an arithmetic expansion fails, POSIX shells either fail the top-level command containing them, or just exit depending on whether they are interactive or not, respectively (see Consequences of Shell Errors for further information). This is so that the shell isn't destructive; the user can immediately go back and amend his script/command without it doing anything unintended due to the bad arithmetic expansion.

When not in POSIX mode, bash behaves as if it's interactive and fail the top-level command containing the bad arithmetic expansion and moves on. In POSIX mode it behaves like other shells and don't print your Exit 1:

$ bash --posix hex_breaks.sh 
Start 1
Start 2
Good Hex: 43981
hex_breaks.sh: line 15: 16#EFGH: value too great for base (error token is "16#EFGH")
$

My lint checker says I should use the $((...)) arithmetic compound instead of "let" because it's safer. However, breaking out of all conditional loops seems to be a completely unexpected side effect of a bad operation!

Your linter is probably talking about ((...)) compound commands; they are more similar to let than $((...)) expansions, and fail only themselves when there is a syntax error in the ... part. Try ((var2 = 16#$bad_hex)) and you'll see.

  • Related