Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

bug? bash select-- typed data is returned as "unbound variable"

Tags:

bash

select

I'm running Linux 3.10.0-693.2.2.el7.x86_64, and for the life of me can't figure why this would happen. It appears to be a bug and shellchecker finds no issues.

#!/bin/bash
set -o nounset

### functions
options=(one two three)

select var in "${options[@]}"; do
    # make sure it is a valid choice
    if (( REPLY <= ${#options[@]} )) && [[ $REPLY  =~ ^[0-9]+$ ]]; then
        case $var in
            one)   exit;;
            two)  df -h /tmp;;
            *)      echo $var;;
        esac
        break
    else
        printf "Invalid selection.\n" >&2
    fi

I used set -xv to trouble shoot, but here's the output without it. In production, the options[@] will be set by a command, and the number they return will be dynamic. So I want the menu to execute a command in *) on $var-- but I have to check for an out of bounds selection in REPLY. Here is the output.

$ bad_select.bash
1) one
2) two
3) three
#? 4
Invalid selection.
#? t
/home/lc5550358/bin/select_menu.wip: line 9: t: unbound variable

The t that I typed? I can also avoid the unbound variable, but entering k=2 or var (the latter is defined in the select). Why and what is the work around (set -o nounset is needed)?

like image 643
SaintHax Avatar asked Dec 08 '17 22:12

SaintHax


People also ask

Why is my variable unbound?

A symbol that has not been given a value by assignment or in a function call is said to be “unbound.” ERROR Unbound variable NEWSYMBOL. The use of “unbound” here is actually a misnomer, because NEWSYMBOL has a binding.

What is Nounset bash?

nounset treats attempts to reference an undefined variable as an error. The following script assumes that an argument is always passed to it: #!/usr/bin/env bash set -o errexit -o nounset.

What does set Nounset do?

But if you use the set option nounset, which causes the shell to indicate an error when it encounters an undefined variable, then you may be interested in unset. will cause the shell to simply print alice. If the variable is undefined, the shell will print a blank line.

How do you set variables in bash?

The easiest way to set environment variables in Bash is to use the “export” keyword followed by the variable name, an equal sign and the value to be assigned to the environment variable.


1 Answers

Inside an arithmetic expression, bash evaluates the value of a variable. Thus, in (( REPLY <= ${#options[@]} )), the value of REPLY is evaluated. If the value of REPLY is t and t is unbound, then that triggers the unbound variable error.

Observe:

$ t=2*3; REPLY=t; echo $(( REPLY + 2 ))
8
$ REPLY=t; t=a+b; a=6; b=c; c=10; echo $(( REPLY + 2 ))
18
$ unset t; REPLY=t; echo $(( REPLY + 2 ))
bash: t: unbound variable

As jm666 explained, one solution is reverse the order of the tests so that arithmetic is done on REPLY only after it is verified that REPLY is an integer.

Alternatively, you can test var instead of REPLY:

#!/bin/bash
set -o nounset

### functions
options=(one two three)

select var in "${options[@]}"; do
    # make sure it is a valid choice
    if [ "$var" ]; then
        case "$var" in
            one)   exit;;
            two)  df -h /tmp;;
            *)      echo "$var";;
        esac
        break
    else
        printf "Invalid selection.\n" >&2
    fi
done

This works because var is only assigned a value if the user provided a valid response at the prompt. Otherwise, var is set to be null.

Documentation

From the man bash section on arithmetic evaluation:

Shell variables are allowed as operands; parameter expansion is performed before the expression is evaluated. Within an expression, shell variables may also be referenced by name without using the parameter expansion syntax. A shell variable that is null or unset evaluates to 0 when referenced by name without using the parameter expansion syn‐ tax. The value of a variable is evaluated as an arithmetic expression when it is referenced, or when a variable which has been given the integer attribute using declare -i is assigned a value. A null value evaluates to 0. A shell variable need not have its integer attribute turned on to be used in an expression.

like image 76
John1024 Avatar answered Sep 29 '22 23:09

John1024