Как проверить, является ли переменная числом в Bash?

Я просто не могу понять, как убедиться, что аргумент, переданный моему скрипту, является числом или нет.

все, что я хочу сделать, это что-то вроде этого:

test *isnumber*  && VAR= || echo "need a number"

помочь?

30 ответов


один подход заключается в использовании регулярного выражения, например:

re='^[0-9]+$'
if ! [[ $yournumber =~ $re ]] ; then
   echo "error: Not a number" >&2; exit 1
fi

Если значение не обязательно является целым числом, рассмотрите возможность изменения регулярного выражения соответствующим образом; например:

^[0-9]+([.][0-9]+)?$

...или, для обработки чисел со знаком:

^[+-]?[0-9]+([.][0-9]+)?$

без bashisms (работает даже в системе в ш),

case $string in
    ''|*[!0-9]*) echo bad ;;
    *) echo good ;;
esac

это отклоняет пустые строки и строки, содержащие не цифры, принимая все остальное.

отрицательные или числа с плавающей запятой нуждаются в дополнительной работе. Идея состоит в том, чтобы исключить - / . в первом" плохом "шаблоне и добавьте больше" плохих " шаблонов, содержащих неуместное их использование (?*-* / *.*.*)


следующее решение также может использоваться в базовых оболочках, таких как Bourne, без необходимости регулярных выражений. В основном любые операции оценки числовых значений, использующие номера, приведут к ошибке, которая будет неявно рассматриваться как false в shell:

"$var" -eq "$var"

в:

#!/bin/bash

var=a

if [ -n "$var" ] && [ "$var" -eq "$var" ] 2>/dev/null; then
  echo number
else
  echo not a number
fi

вы также можете проверить $? код возврата операции, который является более явным:

[ -n "$var" ] && ["$var" -eq "$var"] 2>/dev/null
if [ $? -ne 0 ]; then
   echo $var is not number
fi

перенаправление стандартной ошибки есть, чтобы скрыть сообщение "integer expression expected", которое bash распечатывает в случае, если у нас нет числа.

предостережения (благодаря комментариям ниже):

  • числа с десятичными точками являются не определены как действительные "числа"
  • используя [[ ]] вместо [ ] всегда будет оценено true
  • большинство оболочек без Bash всегда будут оценивать это выражение как true
  • поведение в Bash undocumented и может поэтому изменить без предупреждения
  • если значение включает пробелы после числа (например, "1 a"), возникает ошибка, например bash: [[: 1 a: syntax error in expression (error token is "a")
  • если значение совпадает с VAR-name (например, i= "i"), возникает ошибка, например bash: [[: i: expression recursion level exceeded (error token is "i")

это проверяет, является ли число неотрицательным целым числом и является независимым от оболочки (т. е. без башизмов) и использует только встроенные оболочки:

[ -z "${num##[0-9]*}" ] && echo "is a number" || echo "is not a number";

НО ОШИБАЕТСЯ.
As jilles прокомментировал и предложил в ответ это правильный способ сделать это с помощью оболочки-моделей.

[ ! -z "${num##*[!0-9]*}" ] && echo "is a number" || echo "is not a number";

Я удивлен решениями, непосредственно анализирующими числовые форматы в оболочке. оболочка не очень подходит для этого, являясь DSL для управления файлами и процессами. Есть достаточное количество парсеров немного ниже, например:

isdecimal() {
  # filter octal/hex/ord()
  num=$(printf '%s' "" | sed "s/^0*\([1-9]\)//; s/'/^/")

  test "$num" && printf '%f' "$num" >/dev/null 2>&1
}

измените "%f"на любой требуемый формат.


никто не предложил Баша расширенные шаблону:

[[  == ?(-)+([0-9]) ]] && echo " is an integer"

Я смотрел на ответы... понял, что никто не думал о плавающих числах (с точкой)!

использование grep тоже здорово.
- E означает расширенный regexp
- q означает тихий (не Эхо)
- qE-это сочетание обоих.

чтобы проверить непосредственно в командной строке:

$ echo "32" | grep -E ^\-?[0-9]?\.?[0-9]+$  
# answer is: 32

$ echo "3a2" | grep -E ^\-?[0-9]?\.?[0-9]+$  
# answer is empty (false)

$ echo ".5" | grep -E ^\-?[0-9]?\.?[0-9]+$  
# answer .5

$ echo "3.2" | grep -E ^\-?[0-9]?\.?[0-9]+$  
# answer is 3.2

использование в скрипте bash:

check=`echo "" | grep -E ^\-?[0-9]*\.?[0-9]+$`

if [ "$check" != '' ]; then    
  # it IS numeric
  echo "Yeap!"
else
  # it is NOT numeric.
  echo "nooop"
fi

чтобы соответствовать только целым числам, используйте это:

# change check line to:
check=`echo "" | grep -E ^\-?[0-9]+$`

просто продолжение @mary. Но поскольку у меня недостаточно репутации, я не мог опубликовать это как комментарий к этому сообщению. В любом случае, вот что я использовал:

isnum() { awk -v a="" 'BEGIN {print (a == a + 0)}'; }

функция вернет "1", если аргумент является числом, в противном случае вернет"0". Это работает как для целых чисел, так и для поплавков. Использование что-то вроде:

n=-2.05e+07
res=`isnum "$n"`
if [ "$res" == "1" ]; then
     echo "$n is a number"
else
     echo "$n is not a number"
fi

http://tldp.org/LDP/Bash-Beginners-Guide/html/sect_04_03.html

вы также можете использовать классы символов в bash.

if [[ $VAR = *[[:digit:]]* ]]; then
 echo "$VAR is numeric"
else
 echo "$VAR is not numeric"
fi

цифры будут включать пробел, десятичную точку и "e" или " E " для плавающей точки.

но, если вы укажете шестнадцатеричное число в стиле C, то есть" 0xffff "или" 0XFFFF", [[: digit:]] возвращает true. Немного ловушка здесь, bash позволяет вам делать что-то вроде "0xAZ00" и все еще считать его цифрой (не от какой-то странной причуда компиляторов GCC, которые позволяют использовать 0x-нотацию для баз, отличных от 16???)

вы можете проверить "0x" или " 0X " перед тестированием, если это числовое, если ваш вход полностью ненадежен, если вы не хотите принимать шестнадцатеричные числа. Это было бы достигнуто:

if [[ ${VARIABLE:1:2} = "0x" ]] || [[ ${VARIABLE:1:2} = "0X" ]]; then echo "$VAR is not numeric"; fi

старый вопрос, но я просто хотел закрепить свое решение. Это не требует каких-либо странных трюков с оболочкой или полагается на то, что не было вокруг навсегда.

if [ -n "$(printf '%s\n' "$var" | sed 's/[0-9]//g')" ]; then
    echo 'is not numeric'
else
    echo 'is numeric'
fi

в основном он просто удаляет все цифры из ввода, и если у вас осталась строка ненулевой длины, то это не было число.


Я бы попробовал это:

printf "%g" "$var" &> /dev/null
if [[ $? == 0 ]] ; then
    echo "$var is a number."
else
    echo "$var is not a number."
fi

Примечание: это распознает nan и inf как число.


пока не могу комментировать, поэтому я добавлю свой собственный ответ, который является расширением ответа Гленна Джекмана, используя сопоставление шаблонов bash.

моей первоначальной потребностью было идентифицировать числа и различать целые числа и поплавки. Определения функций вычитаются в:

function isInteger() {
    [[  == ?(-)+([0-9]) ]]
}

function isFloat() {
    [[  == ?(-)@(+([0-9]).*([0-9])|*([0-9]).+([0-9]))?(E?(-|+)+([0-9])) ]]
}

я использовал модульное тестирование (с shUnit2) для проверки моих шаблонов, работающих по назначению:

oneTimeSetUp() {
    int_values="0 123 -0 -123"
    float_values="0.0 0. .0 -0.0 -0. -.0 \
        123.456 123. .456 -123.456 -123. -.456
        123.456E08 123.E08 .456E08 -123.456E08 -123.E08 -.456E08 \
        123.456E+08 123.E+08 .456E+08 -123.456E+08 -123.E+08 -.456E+08 \
        123.456E-08 123.E-08 .456E-08 -123.456E-08 -123.E-08 -.456E-08"
}

testIsIntegerIsFloat() {
    local value
    for value in ${int_values}
    do
        assertTrue "${value} should be tested as integer" "isInteger ${value}"
        assertFalse "${value} should not be tested as float" "isFloat ${value}"
    done

    for value in ${float_values}
    do
        assertTrue "${value} should be tested as float" "isFloat ${value}"
        assertFalse "${value} should not be tested as integer" "isInteger ${value}"
    done

}

Примечания: шаблон isFloat может быть изменен, чтобы быть более терпимым к десятичной точке (@(.,)) и символ E (@(Ee)). Мои модульные тесты проверяют только значения, которые являются целочисленными или плавающими, но не недопустимыми.


[[  =~ ^-?[0-9]+$ ]] && echo "number"

Не забудьте - включить отрицательные числа!


Я использую expr. Он возвращает ненулевое значение, если вы пытаетесь добавить ноль к нечисловое значение:

if expr $number + 0 > /dev/null 2>&1
then
    echo "$number is a number"
else
    echo "$number isn't a number"
fi

можно использовать bc если вам нужны не целые числа, но я не верю bc имеет такое же поведение. Добавление нуля к не-числу дает вам ноль, и он также возвращает значение нуля. Может быть, вы можете объединить bc и expr. Использовать bc добавить ноль к $number. Если ответ 0, затем попробовать expr чтобы проверить, что $number не ноль.


test -z "${i//[0-9]}" && echo digits || echo no no no

${i//[0-9]} заменяет любую цифру в значении $i С пустой строкой, см. man -P 'less +/parameter\/' bash. -z проверяет, если результирующая строка имеет нулевую длину.

если вы также хотите исключить тот случай, когда $i пуст, вы можете использовать одну из этих конструкций:

test -n "$i" && test -z "${i//[0-9]}" && echo digits || echo not a number
[[ -n "$i" && -z "${i//[0-9]}" ]] && echo digits || echo not a number

самый простой способ-проверить, содержит нецифровые символы. Вы заменяете все цифровые символы ничем и проверяете длину. Если есть длина, то это не число.

if [[ ! -n ${input//[0-9]/} ]]; then
    echo "Input Is A Number"
fi

четкий ответ уже был дан @charles Dufy и другими. Чистое решение bash будет использовать следующее:

string="-12,345"
if [[ "$string" =~ ^-?[0-9]+[.,]?[0-9]*$ ]]
then
    echo $string is a number
else
    echo $string is not a number
fi

хотя для вещественных чисел не обязательно иметь номер перед точки радикса.

чтобы обеспечить более тщательную поддержку плавающих чисел и научной нотации (многие программы на C / Fortran или иначе будут экспортировать float таким образом), полезным дополнением к этой строке будет следующее :

string="1.2345E-67"
if [[ "$string" =~ ^-?[0-9]*[.,]?[0-9]*[eE]?-?[0-9]+$ ]]
then
    echo $string is a number
else
    echo $string is not a number
fi

таким образом, что приводит к способу дифференцировать типы чисел, если вы ищете какой-либо конкретный тип :

string="-12,345"
if [[ "$string" =~ ^-?[0-9]+$ ]]
then
    echo $string is an integer
elif [[ "$string" =~ ^-?[0-9]*[.,]?[0-9]*$ ]]
then
    echo $string is a float
elif [[ "$string" =~ ^-?[0-9]*[.,]?[0-9]*[eE]-?[0-9]+$ ]]
then
    echo $string is a scientific number
else
    echo $string is not a number
fi

Примечание: мы могли бы перечислить синтаксические требования для десятичной и научной нотации, один из которых должен позволить запятую как точку радикса, а также ".". Затем мы утверждаем, что должна быть только одна такая точка радикса. В поплавке [Ee] может быть два знака+/ -. Я узнал немного больше правил, с работы Aulu, и испытана против плохих строк, таких как "'- '- E-1 ''0-0'. Вот мои инструменты regex / substring / expr, которые, похоже, держатся:

parse_num() {
 local r=`expr "" : '.*\([.,]\)' 2>/dev/null | tr -d '\n'` 
 nat='^[+-]?[0-9]+[.,]?$' \
 dot="${1%[.,]*}${r}${1##*[.,]}" \
 float='^[\+\-]?([.,0-9]+[Ee]?[-+]?|)[0-9]+$'
 [[ "" == $dot ]] && [[ "" =~ $float ]] || [[ "" =~ $nat ]]
} # usage: parse_num -123.456

Как мне пришлось возиться с этим в последнее время и как karttu это appoach с модульным тестом больше всего. Я пересмотрел код и добавлены некоторые другие решения, попробуйте сами, чтобы увидеть результаты:

#!/bin/bash

    # N={0,1,2,3,...} by syntaxerror
function isNaturalNumber()
{
 [[  =~ ^[0-9]+$ ]]
}
    # Z={...,-2,-1,0,1,2,...} by karttu
function isInteger() 
{
 [[  == ?(-)+([0-9]) ]]
}
    # Q={...,-½,-¼,0.0,¼,½,...} by karttu
function isFloat() 
{
 [[  == ?(-)@(+([0-9]).*([0-9])|*([0-9]).+([0-9]))?(E?(-|+)+([0-9])) ]]
}
    # R={...,-1,-½,-¼,0.E+n,¼,½,1,...}
function isNumber()
{
 isNaturalNumber  || isInteger  || isFloat 
}

bools=("TRUE" "FALSE")
int_values="0 123 -0 -123"
float_values="0.0 0. .0 -0.0 -0. -.0 \
    123.456 123. .456 -123.456 -123. -.456 \
    123.456E08 123.E08 .456E08 -123.456E08 -123.E08 -.456E08 \
    123.456E+08 123.E+08 .456E+08 -123.456E+08 -123.E+08 -.456E+08 \
    123.456E-08 123.E-08 .456E-08 -123.456E-08 -123.E-08 -.456E-08"
false_values="blah meh mooh blah5 67mooh a123bc"

for value in ${int_values} ${float_values} ${false_values}
do
    printf "  %5s=%-30s" $(isNaturalNumber $value) ${bools[$?]} $(printf "isNaturalNumber(%s)" $value)
    printf "%5s=%-24s" $(isInteger $value) ${bools[$?]} $(printf "isInteger(%s)" $value)
    printf "%5s=%-24s" $(isFloat $value) ${bools[$?]} $(printf "isFloat(%s)" $value)
    printf "%5s=%-24s\n" $(isNumber $value) ${bools[$?]} $(printf "isNumber(%s)" $value)
done

Так isNumber () включает тире, запятые и экспоненциальную нотацию и поэтому возвращает TRUE для целых чисел и поплавков, где с другой стороны isFloat() возвращает FALSE для целых значений и isInteger() дополнительно возвращает Ложь на поплавках. Для вашего удобства все как один лайнеры:

isNaturalNumber() { [[  =~ ^[0-9]+$ ]]; }
isInteger() { [[  == ?(-)+([0-9]) ]]; }
isFloat() { [[  == ?(-)@(+([0-9]).*([0-9])|*([0-9]).+([0-9]))?(E?(-|+)+([0-9])) ]]; }
isNumber() { isNaturalNumber  || isInteger  || isFloat ; }

Я использую следующее (для целых чисел):

## ##### constants
##
## __TRUE - true (0)
## __FALSE - false (1)
##
typeset -r __TRUE=0
typeset -r __FALSE=1

## --------------------------------------
## isNumber
## check if a value is an integer 
## usage: isNumber testValue 
## returns: ${__TRUE} - testValue is a number else not
##
function isNumber {
  typeset TESTVAR="$(echo "" | sed 's/[0-9]*//g' )"
  [ "${TESTVAR}"x = ""x ] && return ${__TRUE} || return ${__FALSE}
}

isNumber  
if [ $? -eq ${__TRUE} ] ; then
  print "is a number"
fi

[[ "${var//*([[:digit:]])}" ]]; && echo "$var is not numeric" || echo "$var is numeric"

Он удаляет символ every :digit: class в $var и проверяет, остались ли мы с пустой строкой, что означает, что оригинал был только числами.

что мне нравится в этом, так это его небольшой размер и гибкость. В этой форме это только работает для не разделенных, базовых 10 целых чисел, хотя, конечно, вы можете использовать сопоставление шаблонов для других потребностей.


Quick & Dirty: я знаю, что это не самый элегантный способ, но я обычно просто добавлял к нему ноль и тестировал результат. вот так:

function isInteger {
  [ $((+0)) != 0 ] && echo " is a number" || echo " is not a number"
 }

x=1;      isInteger $x
x="1";    isInteger $x
x="joe";  isInteger $x
x=0x16 ;  isInteger $x
x=-32674; isInteger $x   

$(($1+0)) вернет 0 или бомбу, если $1 не является целым числом. например:

function zipIt  { # quick zip - unless the 1st parameter is a number
  ERROR="not a valid number. " 
  if [ $((+0)) != 0 ] ; then  # isInteger() 
      echo " backing up files changed in the last  days."
      OUT="zipIt--day.tgz" 
      find . -mtime - -type f -print0 | xargs -0 tar cvzf $OUT 
      return 1
  fi
    showError $ERROR
}

примечание: Я думаю, я никогда не думал проверять поплавки или смешанные типы, которые сделают всю бомбу сценария... в моем случае я не хотел, чтобы это зашло дальше. Я собираюсь поиграть с решением мруччи и регулярным выражением Даффи - они кажутся самыми надежными в рамках bash...


Я нашел довольно короткую версию:

function isnum()
{
    return `echo "" | awk -F"\n" '{print ( != +0)}'`
}

  • переменной для проверки

    number=12345 или number=-23234 или number=23.167 или number=-345.234

  • проверить числовые или нечисловые

    echo $number | grep -E '^-?[0-9]*\.?[0-9]*$' > /dev/null

  • принять решение о дальнейших действиях на основе статуса выхода из вышеуказанного

    if [ $? -eq 0 ]; then echo "Numeric"; else echo "Non-Numeric"; fi


чтобы поймать отрицательные числа:

if [[  == ?(-)+([0-9.]) ]]
    then
    echo number
else
    echo not a number
fi

вы можете использовать "let" тоже так:

[ ~]$ var=1
[ ~]$ let $var && echo "It's a number" || echo "It's not a number"
It\'s a number
[ ~]$ var=01
[ ~]$ let $var && echo "It's a number" || echo "It's not a number"
It\'s a number
[ ~]$ var=toto
[ ~]$ let $var && echo "It's a number" || echo "It's not a number"
It\'s not a number
[ ~]$ 

но я предпочитаю использовать оператор "= ~ " Bash 3+, как некоторые ответы в этом потоке.


Я использую printf как другие ответы, упомянутые, если вы поставите строку формата "%f" или "%i " printf сделает проверку за вас. Проще, чем изобретать проверки, синтаксис прост и короток, а printf вездесущ. Так что его достойный выбор на мой взгляд - вы также можете использовать следующую идею, чтобы проверить ряд вещей, его не только полезно для проверки номеров.

declare  -r CHECK_FLOAT="%f"  
declare  -r CHECK_INTEGER="%i"  

 ## <arg 1> Number - Number to check  
 ## <arg 2> String - Number type to check  
 ## <arg 3> String - Error message  
function check_number() { 
  local NUMBER="" 
  local NUMBER_TYPE="" 
  local ERROR_MESG=""
  local -i PASS=1 
  local -i FAIL=0   
  case "${NUMBER_TYPE}" in 
    "${CHECK_FLOAT}") 
        if ((! $(printf "${CHECK_FLOAT}" "${NUMBER}" &>/dev/random;echo $?))); then 
           echo "${PASS}"
        else 
           echo "${ERROR_MESG}" 1>&2
           echo "${FAIL}"
        fi 
        ;;                 
    "${CHECK_INTEGER}") 
        if ((! $(printf "${CHECK_INTEGER}" "${NUMBER}" &>/dev/random;echo $?))); then 
           echo "${PASS}"
        else 
           echo "${ERROR_MESG}" 1>&2
           echo "${FAIL}"
        fi 
        ;;                 
                     *) 
        echo "Invalid number type format: ${NUMBER_TYPE} to check_number()." 1>&2
        echo "${FAIL}"
        ;;                 
   esac
} 

>$ var=45

>$ (($(check_number $var "${CHECK_INTEGER}" "Error: Found $var - An integer is required."))) && { echo "$var+5" | bc; }


мне нравится ответ Альберто Закканьи.

if [ "$var" -eq "$var" ] 2>/dev/null; then

важные предпосылки: - нет подоболочек расплодили - никакие Парсеры RE не вызываются - большинство приложений оболочки не используют реальные числа

но если $var является сложным (например, доступ к ассоциативному массиву), и если число будет неотрицательным целым числом (большинство случаев использования), то это, возможно, более эффективно?

if [ "$var" -ge 0 ] 2> /dev/null; then ..

printf '%b' "-123\nABC" | tr '[:space:]' '_' | grep -q '^-\?[[:digit:]]\+$' && echo "Integer." || echo "NOT integer."

удалить -\? в шаблоне соответствия grep, если вы не принимаете отрицательное целое число.


на Дэвид Вт!--5--> С 13 октября, если используется expr это может быть лучше

test_var=`expr $am_i_numeric \* 0` >/dev/null 2>&1
if [ "$test_var" = "" ]
then
    ......

Если число, умноженное на 1 дает то же значение, (включая отрицательные числа). В противном случае вы получите null который вы можете проверить на


сделал то же самое здесь с регулярным выражением, которое проверяет всю часть и десятичные дроби, разделенные точкой.

re="^[0-9]*[.]{0,1}[0-9]*$"

if [[  =~ $re ]] 
then
   echo "is numeric"
else
  echo "Naahh, not numeric"
fi