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

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

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

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

Любая помощь?

36 ответов

Решение

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

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

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

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

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

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

Без башмизмов (работает даже в Системе В Ш),

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

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

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

232

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

"$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

Перенаправление стандартной ошибки предназначено для того, чтобы скрыть сообщение "ожидается целочисленное выражение", которое распечатывает bash, если у нас нет числа.

ПРЕДОСТЕРЕЖЕНИЯ (благодаря комментариям ниже):

  • Числа с десятичными точками не идентифицируются как действительные "числа"
  • С помощью [[ ]] вместо [ ] всегда буду оценивать true
  • Большинство небашевых оболочек всегда будет оценивать это выражение как true
  • Поведение в Bash недокументировано и поэтому может меняться без предупреждения
  • Если значение включает пробелы после числа (например, "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")
154

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

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

НО НЕПРАВИЛЬНО.
Как прокомментировал и предположил Джилс в своем ответе, это правильный способ сделать это, используя шаблоны оболочки.

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

Никто не предложил расширенное сопоставление с образцом в bash:

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

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

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

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

Измените "%f" на тот формат, который вам требуется.

27

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

Использование grep тоже замечательно.
-E означает расширенное регулярное выражение
-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 "$1" | grep -E ^\-?[0-9]*\.?[0-9]+$`

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

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

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

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

isnum() { awk -v a="$1" '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
11

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
7

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

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

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

6

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

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

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

function isFloat() {
    [[ ${1} == ?(-)@(+([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)). Мои модульные тесты тестируют только значения, которые являются целочисленными или плавающими, но не содержат недопустимых входных данных

5

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

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

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

5
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
4
[[ $1 =~ ^-?[0-9]+$ ]] && echo "number"

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

4

Четкий ответ уже дали @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] может быть два знака +/-. Я узнал еще несколько правил из работы Аулу и проверил на наличие плохих строк, таких как '' '-' '-E-1' '0-0'. Вот мои инструменты regex/substring/expr, которые, кажется, удерживают:

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

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

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

Я использую 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 не ноль

3

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

#!/bin/bash

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

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() также возвращает FALSE для чисел с плавающей запятой. Для вашего удобства все как один лайнер:

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

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

isNumber() {
    (( $1 )) 2>/dev/null
}

Согласно справочной странице это в значительной степени делает то, что я хочу

Если значение выражения не равно нулю, возвращаемое состояние равно 0

Чтобы предотвратить неприятные сообщения об ошибках для строк, которые могут быть числами, я игнорирую вывод ошибок

$ (( 2s ))
bash: ((: 2s: value too great for base (error token is "2s")
1

Я использую 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="${1}" 
  local NUMBER_TYPE="${2}" 
  local ERROR_MESG="${3}"
  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; }

1

Это может быть достигнуто с помощью grep чтобы увидеть, соответствует ли рассматриваемая переменная расширенному регулярному выражению.

Тест целое число 1120:

yournumber=1120
if [ $(echo "$yournumber" | grep -qE '^[0-9]+$'; echo $?) -ne "0" ]; then
    echo "Error: not a number."
else
    echo "Valid number."
fi

Выход: Valid number.

Тест не целое число 1120a:

yournumber=1120a
if [ $(echo "$yournumber" | grep -qE '^[0-9]+$'; echo $?) -ne "0" ]; then
    echo "Error: not a number."
else
    echo "Valid number."
fi

Выход: Error: not a number.


объяснение

  • grep, -E Переключатель позволяет нам использовать расширенное регулярное выражение '^[0-9]+$', Это регулярное выражение означает, что переменная должна только [] содержать числа 0-9 от нуля до девяти от ^ начиная с $ конец переменной и должен иметь как минимум + один персонаж
  • grep, -q Тихий переключатель отключает любой выход независимо от того, находит ли он что-либо.
  • $? это статус выхода предыдущей выполненной команды. Статус выхода 0 означает успех, а все большее означает ошибку. grep команда имеет статус выхода 0 если он найдет совпадение и 1 когда это не так;
  • $() является подоболочкой, которая позволяет нам выполнить другую команду и затем использовать вывод.

Так что все это вместе, в $() под раковиной, мы echo переменная $yournumber а также | передать это grep который с -q переключатель молча соответствует -E расширенное регулярное выражение '^[0-9]+$' выражение. Мы тогда echo $? статус выхода, который будет 0 если grep успешно нашли совпадение и 1 если это не так.

Теперь за пределами $() Раковина и обратно в if условно, мы берем вывод, либо 0 или же 1 от $() недоработать и проверить, если это -ne не равно "0", Если он не соответствует, статус выхода будет 1 который не соответствует "0", Тогда мы будем echo "Error: not a number.", В случае совпадения выходной статус будет 0 который равен "0" и в этом другом случае мы echo "Valid number.",


Для поплавков или пар

Мы можем просто изменить регулярное выражение из '^[0-9]+$' в '^[0-9]*+\.?[0-8]+$' для поплавков или пар.

Тест поплавок 1120.01:

yournumber=1120.01
if [ $(echo "$yournumber" | grep -qE '^[0-9]*+\.?[0-8]+$'; echo $?) -ne "0" ]; then
    echo "Error: not a number."
else
    echo "Valid number."
fi

Выход: Valid number.

Тест поплавок 11.20.01:

yournumber=11.20.01
if [ $(echo "$yournumber" | grep -qE '^[0-9]*+\.?[0-8]+$'; echo $?) -ne "0" ]; then
    echo "Error: not a number."
else
    echo "Valid number."
fi

Выход: Error: not a number.


Для негативов

Чтобы разрешить отрицательные целые числа, просто измените регулярное выражение с '^[0-9]+$' в '^\-?[0-9]+$',

Чтобы разрешить отрицательные числа с плавающей запятой или двойные числа, просто измените регулярное выражение с '^[0-9]*+\.?[0-8]+$' в '^\-?[0-9]*+\.?[0-8]+$',

1

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

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

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

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

1

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

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

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

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

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

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

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

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

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

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

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

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

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

## ##### 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 "$1" | sed 's/[0-9]*//g' )"
  [ "${TESTVAR}"x = ""x ] && return ${__TRUE} || return ${__FALSE}
}

isNumber $1 
if [ $? -eq ${__TRUE} ] ; then
  print "is a number"
fi
1
printf '%b' "-123\nABC" | tr '[:space:]' '_' | grep -q '^-\?[[:digit:]]\+$' && echo "Integer." || echo "NOT integer."

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

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

    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

1

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

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

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

1
Другие вопросы по тегам