Check if $REPLY is in a range of numbers

83,535

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:

  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' "$@" $'\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"
Share:
83,535

Related videos on Youtube

MrVaykadji
Author by

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, 2022

Comments

  • MrVaykadji
    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 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) ?

    • Admin
      Admin over 10 years
      Thank you, it worked well on VLC but Totem doesn't want to read it. I'm switching to libvo_aacenc
  • MrVaykadji
    MrVaykadji over 10 years
    I like the simplicity, but what are the (( ? I tried to use them in prompt and it seems to work like if [ ] ; then but I didn't knew that existed.
  • devnull
    devnull over 10 years
    @MrVaykadji Added a reference from the manual. Let me know if it's not clear.
  • MrVaykadji
    MrVaykadji over 10 years
    Exactly what I was looking for, thank you ! Could I use instead simple comparison expression like >= ?
  • devnull
    devnull over 10 years
    @MrVaykadji Moreover, saying if [ condition ]; then foo; fi is equivalent to saying condition && foo.
  • MrVaykadji
    MrVaykadji over 10 years
    Okay, 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
    llua over 10 years
    You 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 with 10#$REPLY. cmd && cmd isn't quite the same as if cmd; then ... Once you need a else part, chaining logical && and || can cause subtle bugs.
  • devnull
    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
    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
    orion over 10 years
    Bash allows many types of brackets for testing. You have these traditional [ brackets, which work as seen in man 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 out man bash under Compound Commands.
  • MrVaykadji
    MrVaykadji over 10 years
    Oh, I was thinking, should it be ((REPLY>=1)) or (($REPLY>=1)) ?
  • devnull
    devnull over 10 years
    @MrVaykadji You don't need the $ in the arithmetic context.
  • terdon
    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
    Byte Commander over 4 years
    The 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
    terdon over 4 years
    @ByteCommander eeeek! You're absolutely right, thanks.
  • terdon
    terdon over 4 years
    @StéphaneChazelas fixed, thanks.