jeffpkamp
jeffpkamp

Reputation: 2866

Why does a string equal zero in bash test

I have an array which I am using to generate a list which a user can choose from made like this.

list=(a b c d)
n=0
for x in ${list[@]}
    do echo $n\)$x
    n=$((n++)
done
read -p "Pick an item:" choice

I want to allow only valid options to be chosen so I am checking like this.

if [[ $choice -gt ${#list[@]} && $choice -lt -1 ]]
    then ...
else
    echo "not a valid choice"

The issue I am having is all strings evaluate at equal to zero. ie [[ "I am a duck" -eq 0 ]] is True, as is (( "I am a duck" == 0 )). Is there a way to make all string and number comparison evaluate to false? I know I can check for a string with [[ $choice =~ [A-Za-z]+ ]], but I am wondering if there is a way without regular expressions?

EDIT Sorry, I should have tested the "I am a duck" statement before I put it down. It doesn't like the spaces. However, "I_am_a_duck" does evaluate to 0. This explained by chepner below.

Upvotes: 0

Views: 70

Answers (2)

Benjamin W.
Benjamin W.

Reputation: 52371

I would use select for this and not deal with behaviour of strings in arithmetic contexts (as explained in chepner's answer) at all:

list=(a b c d)

PS3='Pick an item: '
select opt in "${list[@]}"; do
    case $opt in
        [abcd]) echo "Choice: $opt ($REPLY)" ;;
        *) echo "Illegal choice" ;;
    esac
done

This will keep looping; to continue after a valid selection, there has to be a break somewhere. The main benefit is select taking care of invalid inputs for you, and you don't have to build the menu yourself.

Upvotes: 1

chepner
chepner

Reputation: 531908

-gt, because it is intended to compare integers, triggers the same behavior for strings as seen in an arithmetic expression: the string is treated as the name of a parameter, and that parameter is expanded (repeat the process if necessary) until you get an integer. If there is no parameter by that name, 0 is substituted.

That said, you could weasel your way out of the problem by number your items starting at one and using

if (( choice < 1 || choice > ${#list[@]} )); then
    echo "not a valid choice"

since now any non-integer inputs will be treated as 0, which is indeed less than the lower limit of 1.

Upvotes: 2

Related Questions