Check if $REPLY is in a range of numbers

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 to

let "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 -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.)

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:

  1. Remove all characters that are not decimal digits (after the sign).
  2. Remove the all the optional leading zeros. Leading zeros will cause the shell to believe that the number is in octal.
  3. 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

0 0 votes
Article Rating
Subscribe
Notify of
guest

0 Comments
Oldest
Newest Most Voted
Inline Feedbacks
View all comments
0
Would love your thoughts, please comment.x
()
x