Check if $REPLY is in a range of numbers
Solution 1
The [
command/shell builtin has comparison tests, so you can just do
if [ "$REPLY" -ge 1 ] && [ "$REPLY" -le 32 ]; then REPLY=-2;
elif [ "$REPLY" -ge 33 ] && [ "$REPLY" -le 48 ]; then REPLY=-1; fi
where -ge
means greater-or-equal-to (and so on). The [
command is just a command, not special syntax (it's actually the same as test
: check out man test
), so it NEEDS the space after it. If you write [$REPLY
it will try to find a command named [$REPLY
and execute it, which won't work. The same goes for closing ]
.
Here, we're using the &&
shell operator to run the second command only if the first is successful. [
also supports -a
to and two tests, but it's deprecated and its usage should be discouraged as it causes arguments not to be parseable reliably.
Edit: to test if the number is integer (if that can happen in your code), first do the test
if [[ "$REPLY" =~ ^[0-9]+$ ]]; then
existing code
else echo "$REPLY is not an integer" >&2 && exit 1; fi
Of course all these bracket expressions return 0 (true) or 1 (false) and can be combined. Not only you can put everything in the same bracket, you can also do
if [[ "$REPLY" =~ ^[0-9]+$ ]] && [ "$REPLY" -ge 1 ] && [ "$REPLY" -le 32 ]; then ...
or something similar.
Solution 2
You could simply say:
((REPLY>=1 && REPLY<=32)) && REPLY=-2
((REPLY>=33 && REPLY<=48)) && REPLY=-1
Quoting from the manual:
((...))
(( expression ))
The arithmetic expression is evaluated according to the rules described below (see Shell Arithmetic). If the value of the expression is non-zero, the return status is 0; otherwise the return status is 1. This is exactly equivalent to
let "expression"
Solution 3
You could do something like this:
#!/usr/bin/env bash
read -p "- Audio Quality [scale from -2 to 10] ? "
if [ -n "$REPLY" ] ; then
ABITRATE="-aq $REPLY"
fi
echo "You chose : $ABITRATE : $REPLY"
## If 0 < $REPLY < 33 and $REPLY is a number
if [[ "$REPLY" =~ ^[0-9]+$ && "$REPLY" -gt 0 && "$REPLY" -lt 33 ]]
then
echo "GOOD"
else
echo "BAD"
fi
Solution 4
First, test whether the input is numeric. For example, using the regular expression match operator of bash conditional expressions:
if [[ $REPLY =~ -?[0-9]+ ]]; then
echo "Invalid input (not numeric): $REPLY"
exit 2
fi
To test numeric ranges, you have two possibilities:
- the
-gt
operator of conditional expressions inside[ … ]
or[[ … ]]
(beware that the<
and>
operators do string comparison, not numeric value comparison, so[[ 10 < 9 ]]
is true); - the usual arithmetic operators inside
((…))
.
Thus:
if ((REPLY >= -2 && REPLY <= 10)); then
: # do nothing -- pass directly to libvorbis
elif ((REPLY <= 24)); then
echo "Value outside supported range: $REPLY"
exit 2
elif ((REPLY <= 135)); then
REPLY=$(((REPLY+8) / 16 - 4))
elif ((REPLY <= 271)); then
REPLY=$(((REPLY+16) / 32))
elif ((REPLY <= 400)); then
REPLY=9
elif ((REPLY <= 707)); then
REPLY=10
else
echo "Value outside supported range: $REPLY"
exit 2
fi
(You may want to use different approximation rules, I don't know if the ones I chose are the best here.)
Solution 5
To correctly detect if an string is a (decimal) number we first need to define what is a decimal integer number. A simple and yet quite complete definition is:
A sequence of an optional sign (+ or -) followed by no more than 18 (significant) decimal digits.
And this steps are needed:
- Remove all characters that are not decimal digits (after the sign).
- Remove the all the optional leading zeros. Leading zeros will cause the shell to believe that the number is in octal.
- Limit the max size of the integer to 18 digits. Below 2**63-1 (max 64 bit integer).
Just one regex will do most of that:
re='^([+-])?0*([0-9]{1,18})$'
[[ $number =~ $re ]] && integer=${BASH_REMATCH[*]:1}
The code to process several numbers is:
#!/bin/bash
DebugLevel=4 # 1:fatal 2:error 3:warn 4:info 5:debug 6:trace
SayMsg (){ local a; a=$1; shift ; # Log level
[[ $a -le $DebugLevel ]] && printf '%s' "$@" $'\n' >&2 ;
}
SayError (){ a=$1; shift; printf '%s' "$@" $'\n' >&2; exit "$a"; }
parseint (){ local re # Parse the first argument as an integer or fail
re='^([+-])?0*([0-9]{1,18})$'
[[ $1 =~ $re ]] || { SayMsg 4 "Invalid number $1"; return 2; }
integer=${BASH_REMATCH[1]}${BASH_REMATCH[2]}
echo "integer=$integer"
}
while read val; do
parseint "$val"
done <<-\_EOT_
0
1
10
100
2345
123456789012345678
923456789012345678
999999999999999999
0000000012345
+023
-00045
-76
""
''
a
abc
1234567890123456789
7.23
-8.17
1e3
10+11
_EOT_
Which will print:
integer=0
integer=1
integer=10
integer=100
integer=2345
integer=123456789012345678
integer=923456789012345678
integer=999999999999999999
integer=12345
integer=+23
integer=-45
integer=-76
Invalid number ""
Invalid number ''
Invalid number
Invalid number a
Invalid number abc
Invalid number 1234567890123456789
Invalid number 7.23
Invalid number -8.17
Invalid number 1e3
Invalid number 10+11
Once that the number is clean and clear, the only missing test is to limit the range of values. This simple couple of lines will do that:
(( 1 <= integer && integer <= 32 )) && REPLY="-2"
(( 33 <= integer && integer <= 48 )) && REPLY="-1"
Related videos on Youtube
![MrVaykadji](https://i.stack.imgur.com/wTyji.png?s=256&g=1)
MrVaykadji
I'm the stereotypical male version of the cat lady. Except I don't own any cat. ... One of those last two sentences was false.
Updated on September 18, 2022Comments
-
MrVaykadji almost 2 years
I'm writing a shell script for Linux, using Bash, to translate any video-file into a MP4. For that, I'm using
avconv
withlibvorbis
for audio.Inside my script, I have a question for the user :
read -p "- Audio Quality [scale from -2 to 10] ? " if [ -n "$REPLY" ] ; then ABITRATE="-aq $REPLY" fi
My "ABITRATE" string goes into the final
avconv
command-line.But I would like to give the user the opportunity to answer that question with a value in Kb (Kilobit), and translate it into the scale that
libvorbis
uses. The "scale from -2 to 10" is this :Quality Kbit/s Normalization ----------------------------- -2 ~32 y -1 ~48 y 0 ~64 y 1 ~80 y 2 ~96 y 3 ~112 y 4 ~128 n 5 ~160 n 6 ~192 n 7 ~224 n 8 ~256 n 9 ~320 n 10 ~500 n
I would like to know how to check if my $REPLY is in a range of number. For example, I would like my script to do something like this :
if [ $REPLY is a number between 1 and 32 ] ; then REPLY="-2" elif [ $REPLY is a number between 33 and 48 ] ; then REPLY="-1" fi
Is this possible (I'm willing to say 'yes of course, shouldn't be hard' but I don't know the syntax to use) ?
-
Admin over 10 yearsThank you, it worked well on VLC but Totem doesn't want to read it. I'm switching to libvo_aacenc
-
-
MrVaykadji over 10 yearsI like the simplicity, but what are the
((
? I tried to use them in prompt and it seems to work likeif [ ] ; then
but I didn't knew that existed. -
devnull over 10 years@MrVaykadji Added a reference from the manual. Let me know if it's not clear.
-
MrVaykadji over 10 yearsExactly what I was looking for, thank you ! Could I use instead simple comparison expression like
>=
? -
devnull over 10 years@MrVaykadji Moreover, saying
if [ condition ]; then foo; fi
is equivalent to sayingcondition && foo
. -
MrVaykadji over 10 yearsOkay, nice ! I would like to accept both your aswers (Orion and you) if I could. Thanks a lot for all this, I learned a lot.
-
llua over 10 yearsYou may want to strip leading zeros if you use this.
a=08; (( a > 1 ))
will error since 08 is considered octal. you could also force decimal with10#$REPLY
.cmd && cmd
isn't quite the same asif cmd; then ...
Once you need aelse
part, chaining logical&&
and||
can cause subtle bugs. -
devnull over 10 years@MrVaykadji It's good to have multiple answers to a question. You'll often find that those provide different perspectives to solving a problem. Good that you learnt something from the various answers that you got.
-
devnull over 10 years@llua In cases as simple, short-circuiting is pretty much equivalent. Thanks for the tip about numbers with leading zeros being treated as octal; that would happen in pretty much every language!
-
orion over 10 yearsBash allows many types of brackets for testing. You have these traditional
[
brackets, which work as seen inman test
. These are traditional and fool-proof. Then, you have a lot of bash builtins. You have[[
which are similar, but not exactly the same, as this one doesn't expand pathnames (there, < = > mean string comparisons, and integer comparisons are the same as in[
). Both also have a lot of tests for file existence, permissions and so on. Then you have single(
and double((
used in @devnull's answer. Check outman bash
underCompound Commands
. -
MrVaykadji over 10 yearsOh, I was thinking, should it be
((REPLY>=1))
or(($REPLY>=1))
? -
devnull over 10 years@MrVaykadji You don't need the
$
in the arithmetic context. -
terdon over 10 years@MrVaykadji I highly recommend you also test whether the variable is a number, you might get unexpected results otherwise:
foo='a'; [[ "$foo" -lt 32 ]] && echo yes
-
Byte Commander over 4 yearsThe regular expression must not be quoted, otherwise it's treated as literal string. You're also only matching single digit numbers. It should be
[[ ... && "$REPLY" =~ ^[0-9]+$ ]]
, I think? -
terdon over 4 years@ByteCommander eeeek! You're absolutely right, thanks.
-
terdon over 4 years@StéphaneChazelas fixed, thanks.