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)?
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.
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.
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.
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.
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.
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.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With