until
printf 'Please enter 1 or 2: '
IFS= read -r line || exit # exit on EOF
[ "$line" = 1 ] || [ "$line" = 2 ]
do
printf >&2 '"%s" is neither 1 nor 2, try again.\n' "$line"
done
With IFS=
and -r
, the input line is stored as-is in $line
. You may want to omit the IFS=
so that blanks (assuming you've not modified $IFS
) are automatically stripped from the beginning or end of the input.
If the input has to be the 1
or 2
strings only, then you want to use =
, not -eq
.
If you want to allow other expressions of the 1
or 2
numbers, like 01
, 1+1
, 1.0
, 100e-2
, exp(0)
, RANDOM
(sometimes), then you could use -eq
, but note that not all shells accept all types of expressions above (ksh93
will accept them all, but bash
would only accept things like 1
(leading and/or trailing blanks), or 0001
(leading zeros)), and that means you'll get error messages for inputs that don't form valid arithmetic expressions.
With shells that interpret arithmetic expressions, that's also dangerous as that can change variable values (like for an input like PATH=123
) or even run arbitrary commands (like for inputs like a[0$(cmd>&2)]
).
Depending on the shell, you could also get false positives on 18446744073709551617 or 4294967297 (or any other multiple of 232 or 264 + 1 or 2) as most shells use 64bit or 32bit integer numbers.
Whatever you do, do not use the -o
and -a
binary test
/[
operators. Those should be banished as they make the parsing of the [
command potentially ambiguous and in practice unreliable (and are now marked obsolete in the POSIX spec).
For instance:
$ line='!' sh -c '[ "$line" = 1 -o "$line" = 2 ]'
sh: line 0: [: too many arguments
And remember to quote your variables ("$line"
, not $line
which would undergo split+glob).