I’m writing a shell script for Linux, using Bash, to translate any video-file into a MP4. For that, I’m using avconv with libvorbis 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) ?
Answers:
Thank you for visiting the Q&A section on Magenaut. Please note that all the answers may not help you solve the issue immediately. So please treat them as advisements. If you found the post helpful (or not), leave a comment & I’ll get back to you as soon as possible.
Method 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.
Method 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 tolet "expression"
Method 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
Method 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
-gtoperator 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.)
Method 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' "<a href="https://getridbug.com/cdn-cgi/l/email-protection" class="__cf_email__" data-cfemail="d3f793">[email protected]</a>" $'n' >&2 ;
}
SayError (){ a=$1; shift; printf '%s' "<a href="https://getridbug.com/cdn-cgi/l/email-protection" class="__cf_email__" data-cfemail="163256">[email protected]</a>" $'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"
All methods was sourced from stackoverflow.com or stackexchange.com, is licensed under cc by-sa 2.5, cc by-sa 3.0 and cc by-sa 4.0