Як перевірити, чи є змінною число в Bash?


598

Я просто не можу зрозуміти, як переконатися, що аргумент, переданий моєму сценарію, є числом чи ні.

Все, що я хочу зробити, - це щось подібне:

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

Будь-яка допомога?


17
Як осторонь - test && echo "foo" && exit 0 || echo "bar" && exit 1підхід, який ви використовуєте, може мати деякі непередбачувані побічні ефекти - якщо відлуння не вдасться (можливо, вихід виходить із закритого FD), exit 0пропуск буде пропущений, і код спробує echo "bar". Якщо це теж &&не вдасться, умова вийде з ладу, і вона навіть не виконається exit 1! Використання фактичних ifтверджень, а не &&/ ||менш схильне до несподіваних побічних ефектів.
Чарльз Даффі

@CharlesDuffy Ось така справді розумна думка, до якої більшість людей дістається лише тоді, коли їм доводиться відслідковувати волохатих помилок ...! Я ніколи не думав, що відлуння може повернути невдачу.
Каміло Мартін,

6
Трохи спізнившись на вечірку, але я знаю про небезпеку, про яку писав Чарльз, оскільки мені довелося пережити їх ще досить давно. Тож ось для вас стовідсотковий (і добре читабельний) рядок: [[ $1 =~ "^[0-9]+$" ]] && { echo "number"; exit 0; } || { echo "not a number"; exit 1; }фігурні дужки вказують на те, що речі НЕ повинні виконуватись у підкладинках (що, безумовно, було б таким чином із ()скобками, що використовуються замість них). Caveat: Ніколи не пропускайте остаточну крапку з комою . В іншому випадку ви можете змусити bashроздрукувати найгубніші (і безглузді) повідомлення про помилки ...
syntaxerror

5
Вона не працює в Ubuntu, якщо ви не видалите лапки. Так і просто повинно бути[[ 12345 =~ ^[0-9]+$ ]] && echo OKKK || echo NOOO
Тревіньо

4
Вам потрібно буде бути більш конкретним щодо того, що ви маєте на увазі під цифрою . Ціле число? Фіксований номер? Наукові позначення ("е")? Чи є необхідний діапазон (наприклад, 64-бітне неподписане значення), чи ви дозволяєте записати будь-яке число?
Toby Speight

Відповіді:


803

Один із підходів - використовувати регулярний вираз, як-от так:

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

Якщо значення не обов'язково є цілим числом, подумайте про коригування регулярного вираження; наприклад:

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

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

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

7
+1 для цього підходу, але будьте обережні з десятковими колами, роблячи цей тест, наприклад, з помилкою "1,0" або "1,0" друкує: Не число ".
sourcerebels

15
Я знаходжу '' exec> & 2; відлуння ... '' досить нерозумно. Просто '' відлуння ...> & 2 ''
lhunath

5
@Ben ви дійсно хочете обробити більше ніж один знак мінус? Я б зробив це ^-?замість цього, ніж ^-*якщо ви насправді не виконуєте роботу, щоб правильно обробити кілька інверсій.
Чарльз Даффі

4
@SandraSchlichting Змушує весь майбутній вихід перейти на stderr. Насправді тут немає сенсу, де є лише одне відлуння, але я звик потрапляти у випадки, коли повідомлення про помилки охоплює кілька рядків.
Чарльз Даффі

29
Я не впевнений, чому регулярний вираз має бути збережений у змінній, але якщо це заради сумісності, я не думаю, що це потрібно. Ви можете просто застосувати вираз безпосередньо: [[ $yournumber =~ ^[0-9]+$ ]].
konsolebox

282

Без башизмів (працює навіть у Системі V sh),

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

Це відхиляє порожні рядки та рядки, що містять нецифрові цифри, приймаючи все інше.

Негативні чи числа з плаваючою комою потребують додаткової роботи. Ідея полягає в тому, щоб виключити -/ .в перший "поганий" шаблон і додати більше "поганих" шаблонів, що містять нераціональне використання їх ( ?*-*/ *.*.*)


20
+1 - це ідіоматичний, портативний шлях назад до оригінальної оболонки Борна і має вбудовану підтримку мак-шаблонів у стилі глобус. Якщо ви приїхали з іншої мови програмування, це виглядає жахливо, але це набагато елегантніше, ніж впоратися з крихкістю різних питань котирування та нескінченними проблемами сумісності назад / збокуif test ...
tripleee

6
Ви можете змінити перший рядок на ${string#-}(який не працює в антикварних оболонках Борна, але працює в будь-якій оболонці POSIX), щоб прийняти негативні цілі числа.
Жил "ТАК - перестань бути злим"

4
Крім того, це легко поширити на поплавці - просто додайте '.' | *.*.*до заборонених шаблонів і додайте крапку до дозволених символів. Так само ви можете дозволити необов'язковий знак раніше, хоча тоді я вважаю за краще case ${string#[-+]}просто ігнорувати знак.
трійка

Дивіться це для обробки підписаних цілих чисел: stackoverflow.com/a/18620446/2013911
Niklas Peter

2
@Dor Цитати не потрібні, оскільки команда case ні в якому разі не виконує розбиття слів і генерування імені для цього слова. (Однак для розширення шаблонів випадків може знадобитися цитування, оскільки воно визначає, чи символи, що відповідають шаблону, є буквальними чи спеціальними.)
jilles

193

Наступне рішення також може бути використане в основних оболонках, таких як Борн, без необхідності регулярних виразів. В основному будь-які операції оцінювання числових значень, що використовують нечисло, призведе до помилки, яка неявно вважатиметься помилковою в оболонці:

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

8
Я все-таки рекомендую це (але з цитованими змінними, щоб забезпечити порожні рядки), оскільки результат гарантовано може бути використаний як число в Bash, незалежно від того.
l0b0

21
Подбайте про використання одинарних кронштейнів; [[ a -eq a ]]оцінюється в істину (обидва аргументи перетворюються на нуль)
Tgr

3
Дуже хороший! Зверніть увагу, це працює лише для цілого числа, а не для будь-якого числа. Мені потрібно було перевірити наявність одного аргументу, який повинен бути цілим числом, тому це спрацювало добре:if ! [ $# -eq 1 -o "$1" -eq "$1" ] 2>/dev/null; then
haridsv

6
Я б настійно не рекомендував цей метод через несуттєву кількість оболонок, [вбудований в яких буде оцінювати аргументи як арифметичні. Це вірно і в ksh93, і в mksh. Крім того, оскільки обидва ці масиви підтримують, існує проста можливість для введення коду. Використовуйте замість шаблону.
ormaaj

3
@AlbertoZaccagni, в поточних випусках bash ці значення інтерпретуються правилами числового контексту лише для, [[ ]]але не для [ ]. Однак, ця поведінка не визначена як стандартом POSIX для, так testі власною документацією Bash; майбутні версії bash можуть модифікувати поведінку так, щоб вона відповідала ksh, не порушуючи жодних документально підтверджених поведінкових обіцянок, тому покладатися на її поточну поведінку, що зберігається, не гарантується безпечно.
Чарльз Даффі

43

Цей тест перевіряє, якщо число невід'ємне ціле число і обоє оболонку незалежно (тобто без башизмів) і використовує лише вбудовані оболонки:

НЕПРАВИЛЬНО.

Оскільки ця перша відповідь (нижче) передбачає цілі числа з символами в них до тих пір, поки перші не будуть першими в змінній.

[ -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";

5
Це не працює належним чином, він приймає будь-який рядок, починаючи з цифри. Зауважте, що WORD у $ {VAR ## WORD} і подібних - це шаблон оболонки, а не регулярний вираз.
jilles

2
Чи можете ви перекласти цей вираз англійською мовою? Я дуже хочу його використовувати, але я не розумію його достатньо, щоб довіряти йому, навіть після ознайомлення з сторінкою bash man.
CivFan

2
*[!0-9]*- це шаблон, який відповідає всім рядкам, що мають принаймні 1 нецифровий символ. ${num##*[!0-9]*}це "розширення параметрів", де ми беремо вміст numзмінної та видаляємо найдовший рядок, що відповідає шаблону. Якщо результат розширення параметра не порожній ( ! [ -z ${...} ]), то це число, оскільки він не містить жодного нецифрового символу.
mrucci

На жаль, це не вдається, якщо в аргументі є будь-які цифри, навіть якщо це невірне число. Наприклад, "exam1ple" або "a2b".
studgeek

Обидва провалюються122s :-(
Гастур

40

Ніхто не запропонував розгорнутий узор моделі Bash :

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

5
Гленн, я shopt -s extglobвидаляю з вашої публікації (що я схвалив, це одна з моїх улюблених відповідей тут), оскільки в умовних конструкціях ви можете прочитати: Коли використовуються оператори ==та !=оператори, рядок праворуч від оператора вважається шаблоном і відповідає згідно з правилами, описаними нижче в " Шаблон узгодження" , як якщо б extglobвключена опція оболонки. Сподіваюся, ви не заперечуєте!
gniourf_gniourf

У таких контекстах вам не потрібно shopt extglob... це добре знати!
gniourf_gniourf

Добре працює для простих цілих чисел.

2
@Jdamian: Ви маєте рацію, це було додано в Bash 4.1 (який був випущений наприкінці 2009 року ... Bash 3.2 був випущений у 2006 році ... тепер це антикварне програмне забезпечення, вибачте за тих, хто застряг у минулому). Крім того, ви можете стверджувати, що екглоб, де представлений у версії 2.02 (випущений у 1998 р.), І не працює у версії <2.02 ... Тепер ваш коментар буде служити застереженням щодо старих версій.
gniourf_gniourf

1
Змінні всередині [[...]]не підлягають розбиткові слів або розширенню глобальної форми.
glenn jackman

26

Я здивований рішенням, що безпосередньо аналізують формати чисел у оболонці. оболонка не дуже підходить для цього, тому що це 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" на потрібний формат.


4
isnumber(){ printf '%f' "$1" &>/dev/null && echo "this is a number" || echo "not a number"; }
Жилль Кінот

4
@sputnick ваша версія порушує властиву (і корисну) семантику повернення значення вихідної функції. Таким чином, замість того, щоб просто залишити функцію як є, і використовувати його:isnumber 23 && echo "this is a number" || echo "not a number"
michael

4
Чи не повинно це також бути 2>/dev/null, щоб isnumber "foo"це не забруднювало складніше?
gioele

4
Називати сучасні оболонки типу bash "DSL для управління файлами та процесами" - це ігнорування того, що вони використовуються набагато більше, ніж це - деякі дистрибутиви побудували на ньому цілі менеджери пакетів та веб-інтерфейси (настільки потворні, як це може бути). Пакетні файли відповідають вашому опису, оскільки навіть встановити змінну там важко.
Каміло Мартін

7
Смішно, що ти намагаєшся бути розумним, копіюючи якісь ідіоми з інших мов. На жаль, це не працює в оболонках. Оболонки дуже особливі, і без ґрунтовних знань про них, ви, ймовірно, пишете зламаний код. Ваш код порушений: isnumber "'a"повернеться істинно. Це задокументовано в специфікації POSIX, де ви прочитаєте: Якщо головний символ - це одноцінна або подвійна, значення має бути числовим значенням в базовому кодовому наборі символу, що слідує за одноцитатою або подвійною цитатою. .
gniourf_gniourf

16

Я дивився на відповіді і ... зрозумів, що ніхто не думає про номери FLOAT (з крапкою)!

Використання 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 "$1" | 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 "$1" | grep -E ^\-?[0-9]+$`

Рішення, що використовують awk by triple_r та tripleee, працюють з поплавками.
Кен Джексон

Дякую за це і дуже вдалий момент! Причина полягає в тому, як перевірити, чи це число, а не просто ціле число.
Танасіс

Я вам теж дякую Танасіс! Давайте допомагати один одному завжди.
Серхіо Абреу

12

Лише наступні дії до @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

4
Роздрукувати номер менш корисно, ніж встановити вихідний код. 'BEGIN { exit(1-(a==a+0)) }'трохи важко обробляти, але його можна використовувати у функції, яка повертає справжнє або помилкове так само [, grep -qі т. д.
tripleee

9
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

Великі пальці вгору спеціально для man -P 'less +/parameter\/' bashчастини. Вивчаючи щось нове щодня. :)
Девід

@sjas Ви можете легко додати \-регулярний вираз для вирішення проблеми. Використовуйте [0-9\-\.\+]для обліку плавців та підписаних чисел.
user2683246

@sjas гаразд, моя вина
user2683246

@sjasecho $i | python -c $'import sys\ntry:\n float(sys.stdin.read().rstrip())\nexcept:\n sys.exit(1)' && echo yes || echo no
user2683246

9

Для своєї проблеми мені потрібно було лише переконатися, що користувач випадково не вводить текст, тому я намагався зробити його простим і читабельним

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

На думку чоловічої сторінки, це майже все, що я хочу

Якщо значення виразу не дорівнює нулю, стан повернення дорівнює 0

Щоб запобігти неприємним повідомленням про помилки для рядків, які "можуть бути числами", я ігнорую вихід помилки

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

Це більше нагадує isInteger. Але дивовижна подяка!
Нахель

8

Старе питання, але я просто хотів зайнятися своїм рішенням. Цей не вимагає жодних дивних хитрощів з оболонками або покладатися на те, що не існувало вічно.

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

В основному він просто видаляє всі цифри з вхідних даних, і якщо вам залишилося рядок з нульовою довжиною, це не було число.


Це не вдається для порожнього var.
gniourf_gniourf

Або для змінних, що містять нові рядки чи щось подібне $'0\n\n\n1\n\n\n2\n\n\n3\n'.
gniourf_gniourf

7

Ще не можу коментувати, тому я додам свою власну відповідь, яка є розширенням до відповіді Глена Джекмана за допомогою відповідності шаблону 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)). Мій блок тестує тестування лише значень, які є цілими чи плаваючими, але не є недійсним введенням.


Вибачте, не зрозумів, як призначена функція редагування.
3ronco

6

Я б спробував це:

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

Примітка: це розпізнає nan та inf як число.


2
або дублікат, або, можливо, краще підходить як коментар до відповіді pixelbeat (використовуючи %f, мабуть, краще все-таки)
michael

3
Замість перевірки попереднього статусного коду, чому б просто не помістити його в ifсебе? Ось що ifробить ...if printf "%g" "$var" &> /dev/null; then ...
Каміло Мартін,

3
Це є й інші застереження. Це підтвердить порожній рядок та рядки на зразок 'a.
gniourf_gniourf

6

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

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 або ще експортуватимуть плавати таким чином), корисним доповненням до цього рядка буде наступне:

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

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

Не забудьте -включити від’ємні цифри!


Яка мінімальна версія bash? Я просто отримую bash: умовний бінарний оператор очікується bash: синтаксична помилка поблизу несподіваного маркера `= ~ '
Пол Харгрівз

1
@PaulHargreaves =~існував принаймні, як баш 3.0.
Жил 'SO- перестань бути злим'

@PaulHargreaves, напевно, у вас виникли проблеми з вашим першим операндом, наприклад, занадто багато лапок чи подібних
Джошуа Клейтон

@JoshuaClayton Я запитав про версію, оскільки це дуже старий баш на коробці Solaris 7, який у нас все ще є, і він не підтримує = ~
Пол Харгрівз

6

Цього можна досягти, використовуючи, grepщоб побачити, чи відповідає змінна, про яку йдеться, з розширеним регулярним виразом.

Ціле тест 1120:

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

Вихід: Valid number.

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

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

Вихід: Error: not a number.


Пояснення

  • grep, То -Eперемикач дозволяє використовувати розширені регулярні вирази '^[0-9]+$'. Цей регулярний вираз означає, що змінна повинна []містити лише числа від 0-9нуля до дев'яти від ^початку до $кінця змінної і повинна мати принаймні +один символ.
  • grep, То -qперемикач тихо відключає будь-який вихід знаходить він чи ні нічого.
  • ifперевіряє стан виходу grep. Статус виходу 0означає успіх, і все, що більше, означає помилку. grepКоманда має статус завершення , 0якщо він знаходить збіг , і 1коли він не робить;

Таким чином, склавши це все разом, в ifтесті ми змінюємо echoзмінну $yournumberі передаємо |її до grepякої з -qперемикачем мовчки відповідає-E розширеним регулярним виразом '^[0-9]+$'вираження. Статус виходу grepбуде, 0якщо grepуспішно знайдено збіг, а 1якщо він не відбувся. Якщо вдалося відповідати, ми echo "Valid number.". Якщо не вдалося збігтися, ми echo "Error: not a number.".


Для поплавців або пар

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

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

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

Вихід: Valid number.

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

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

Вихід: Error: not a number.


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

Щоб дозволити негативні цілі числа, просто змініть регулярний вираз з '^[0-9]+$' на'^\-?[0-9]+$' .

Щоб дозволити негативне плавання чи подвійне значення, просто змініть регулярний вираз з '^[0-9]*\.?[0-9]+$'на '^\-?[0-9]*\.?[0-9]+$'.


Ти маєш рацію, @CharlesDuffy. Дякую. Я прибрав відповідь.
Джозеф Ших

Право ви знову, @CharlesDuffy. Виправлено!
Джозеф Ших

1
LGTM; відповідь у редагуванні має мій +1. Єдине, що я б робив інакше на даний момент, - це лише питання думки, а не коректність (f / e, використання [-]замість \-і [.]замість, \.є трохи більш багатослівним, але це означає, що ваші рядки не повинні змінюватися, якщо вони ' повторно використовується в контексті, коли зворотні косої риси споживаються).
Чарльз Даффі

4

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

17
[[ $VAR = *[[:digit:]]* ]]повертає істину , якщо змінна містить число, тільки якщо це НЕ є цілим числом.
glenn jackman

[[ "z3*&" = *[[:digit:]]* ]] && echo "numeric"відбитки numeric. Випробуваний у версії bash 3.2.25(1)-release.
Jdamian

1
@ultraswadable, ваше рішення виявляє ті рядки, що містять принаймні одну цифру, оточену (або ні) будь-якими іншими символами. Я відмовився.
Jdamian

Отже, очевидно правильний підхід полягає в тому, щоб змінити це і використовувати[[ -n $VAR && $VAR != *[^[:digit:]]* ]]
eschwartz

@eschwartz, ваше рішення не працює з негативними числами
Ангел

4

Найпростіший спосіб - перевірити, чи містить він нецифрові символи. Ви замінюєте всі цифри символів нічим і перевіряєте їх довжину. Якщо є довжина, це не число.

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

2
Для поводження з негативними цифрами знадобиться складніший підхід.
Енді

... Або необов'язковий позитивний знак.
tripleee

@tripleee Я хотів би бачити ваш підхід, якщо ви знаєте, як це зробити.
Енді

4

Як мені довелося останнім часом втручатися в це, і найбільше подобається набір картту з тестовою одиницею. Я переглянув код і додав ще деякі рішення, спробуйте його самі, щоб побачити результати:

#!/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; }

Особисто я б видалив functionключове слово, оскільки воно не робить нічого корисного. Також я не впевнений у корисності повернутих значень. Якщо не вказано інше, функції повернуть статус виходу останньої команди, тому returnсамостійно нічого не потрібно .
Том Фенех

Приємно, справді returnце плутають і роблять його менш читабельним. Використання functionключових слів чи ні - це більше питання особистого смаку, принаймні я видалив їх із однієї вкладиші, щоб заощадити місце. Дякую.
3ronco

Не забувайте, що після тестів для однолінійних версій потрібні крапки з комою.
Том Фенех

2
isNumber поверне 'true' на будь-який рядок, який має в ньому число.
DrStrangepork

@DrStrangepork Дійсно, у моєму масиві false_values ​​відсутній цей випадок. Я буду дивитись на це. Дякую за підказку.
3ronco

4

Я використовую 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це не нуль.


1
Це досить погано. Щоб зробити його трохи краще, вам слід скористатися expr -- "$number" + 0; але це все одно буде робити вигляд 0 isn't a number. Від man expr:Exit status is 0 if EXPRESSION is neither null nor 0, 1 if EXPRESSION is null or 0,
gniourf_gniourf

3

Мені подобається відповідь Альберто Закканні.

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

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

Але якщо $varскладний (наприклад, доступ до асоціативного масиву) і якщо число буде невід'ємним цілим числом (більшість випадків використання), то це, можливо, є більш ефективним?

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

2

Я використовую 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; }


2

регулярне вираження vs розширення параметра

Як відповідь Чарльза Даффі працює, але тільки використовуватирегулярний вираз , і я знаю, що це повільно, я хотів би показати інший спосіб, використовуючи лишеРозширення параметра :

Лише для додатних цілих чисел:

is_num() { [ "$1" ] && [ -z "${1//[0-9]}" ] ;}

Для цілих чисел:

is_num() { 
    local chk=${1#[+-]};
    [ "$chk" ] && [ -z "${chk//[0-9]}" ]
}

Потім для плаваючого:

is_num() { 
    local chk=${1#[+-]};
    chk=${chk/.}
    [ "$chk" ] && [ -z "${chk//[0-9]}" ]
}

Щоб відповідати початковому запиту:

set -- "foo bar"
is_num "$1" && VAR=$1 || echo "need a number"
need a number

set -- "+3.141592"
is_num "$1" && VAR=$1 || echo "need a number"

echo $VAR
+3.141592

Тепер невелике порівняння:

Існує така ж перевірка, заснована на відповіді Чарльза Даффі :

cdIs_num() { 
    local re='^[+-]?[0-9]+([.][0-9]+)?$';
    [[ $1 =~ $re ]]
}

Деякі тести:

if is_num foo;then echo It\'s a number ;else echo Not a number;fi
Not a number
if cdIs_num foo;then echo It\'s a number ;else echo Not a number;fi
Not a number
if is_num 25;then echo It\'s a number ;else echo Not a number;fi
It's a number
if cdIs_num 25;then echo It\'s a number ;else echo Not a number;fi
It's a number
if is_num 3+4;then echo It\'s a number ;else echo Not a number;fi
Not a number
if cdIs_num 3+4;then echo It\'s a number ;else echo Not a number;fi
Not a number
if is_num 3.1415;then echo It\'s a number ;else echo Not a number;fi
It's a number
if cdIs_num 3.1415;then echo It\'s a number ;else echo Not a number;fi
It's a number

Гаразд, це добре. Тепер скільки часу займе все це (На моєму малиновому пі):

time for i in {1..1000};do is_num +3.14159265;done
real    0m2.476s
user    0m1.235s
sys     0m0.000s

Потім з регулярними виразами:

time for i in {1..1000};do cdIs_num +3.14159265;done
real    0m4.363s
user    0m2.168s
sys     0m0.000s

Я погоджуюся, у будь-якому випадку я вважаю за краще не використовувати регулярний вираз, коли я міг би використовувати розширення параметрів ... Зловживання RE зробить скрипт bash повільніше
Ф. Хаурі

Відповідь відредаговано: Не потрібно extglob(і трохи швидше)!
Ф. Хаурі

@CharlesDuffy, на моєму малиновому пі, 1000 ітерацій займе 2,5 сек з моєю версією та 4,4 сек з вашою версією!
Ф. Хаурі

Не можу з цим посперечатися. 👍
Чарльз Даффі

1

Щоб зловити від’ємні числа:

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

Крім того, для цього потрібно вперше включити розширений глобул. Це функція лише Bash, яка вимкнена за замовчуванням.
tripleee

@tripleee Розширений глобус активується автоматично при використанні == або! = When the ‘==’ and ‘!=’ operators are used, the string to the right of the operator is considered a pattern and matched according to the rules described below in Pattern Matching, as if the extglob shell option were enabled. gnu.org/software/bash/manual/bashref.html#index-_005b_005b
Badr

@BadrElmers Дякую за оновлення. Це здається новою поведінкою, що не відповідає дійсності мого Bash 3.2.57 (MacOS Mojave). Я бачу, це працює так, як ви описуєте в 4.4.
трійчатка

1

Ви можете також використовувати "нехай":

[ ~]$ 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+, як деякі відповіді в цій темі.


5
Це дуже небезпечно. Не оцінюйте невірну арифметику в оболонці. Спершу його слід підтвердити іншим способом.
ormaaj

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

Видаліть -\?узор відповідності grep, якщо ви не приймаєте від'ємне ціле число.


2
Відсутність пояснення через відсутність пояснень. Як це працює? Це виглядає складно і крихко, і не очевидно, які вклади він точно прийме. (Наприклад, чи важливо видаляти пробіли? Чому? Це буде говорити, що число із вбудованими пробілами є дійсним числом, що може бути не бажаним.)
tripleee

1

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

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

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

Чи можете ви пояснити, чому ваша відповідь принципово відрізняється від інших старих відповідей, наприклад, відповіді Чарльза Даффі? Що ж, ваша відповідь насправді зламана, оскільки вона підтверджує єдиний період.
gniourf_gniourf

не впевнений, що тут розуміють єдиний період ... очікується один або нульовий період .... Але правильно нічого принципово не відрізняється, просто знайшов регулярний вираз легше читати.
Ієронім

також використання * повинно відповідати більш реальним справ
Ієронім

Річ у тому, що ви співставляєте порожній рядок a=''і рядок, який містить лише період, a='.'щоб ваш код трохи порушений ...
gniourf_gniourf

0

Я використовую наступне (для цілих чисел):

## ##### 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

Практично правильний (ви приймаєте порожню рядок), але граціозно складний до точки затуманення.
Жил "ТАК - перестань бути злим"

2
Неправильно: ви приймаєте -nі т. Д. (Через echo), і ви приймаєте змінні з останніми рядками (через $(...)). І, до речі, printне є дійсною командою оболонки.
gniourf_gniourf

0

Я спробував рецепт ультразйомки, як це здалося мені найбільш практичним, і не міг змусити його працювати. Зрештою, я придумав інший спосіб, хоча, заснований як інші на заміні параметрів, цього разу із заміною регулярних виразів:

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

Він видаляє кожен: digit: символ класу в $ var і перевіряє, чи залишилися ми з порожнім рядком, тобто в оригіналі були лише числа.

Що мені подобається в цьому, є його невеликий слід і гнучкість. У цій формі він працює лише для необмежених базових 10 цілих чисел, хоча, безумовно, ви можете використовувати відповідність шаблону, щоб відповідати іншим потребам.


Читаючи рішення mrucci, воно виглядає майже так само, як і моє, але використовуючи регулярну заміну рядків замість "sed". Обидва використовують однакові правила для відповідності шаблонів і є, AFAIK, взаємозамінними рішеннями.
ота

sedє POSIX, тоді як ваше рішення bash. Обидва мають своє використання
v010dya

0

Швидке та брудне: я знаю, що це не найелегантніший спосіб, але я зазвичай просто додаю нуль і перевіряю результат. так:

function isInteger {
  [ $(($1+0)) != 0 ] && echo "$1 is a number" || echo "$1 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 [ $(($1+0)) != 0 ] ; then  # isInteger($1) 
      echo " backing up files changed in the last $1 days."
      OUT="zipIt-$1-day.tgz" 
      find . -mtime -$1 -type f -print0 | xargs -0 tar cvzf $OUT 
      return 1
  fi
    showError $ERROR
}

ПРИМІТКА: Я думаю, я ніколи не думав перевіряти наявність поплавців чи змішаних типів, які зроблять всю скрипт-бомбу ... в моєму випадку я не хотів, щоб вона йшла далі. Я пограю з рішенням mrucci і регексом Даффі - вони здаються найбільш надійними в рамках башу ...


4
Це приймає арифметичні вирази на зразок 1+1, але відхиляє деякі додатні цілі числа з провідними 0s (тому що 08є невірною восьмеричною постійною).
Жил "ТАК - перестань бути злим"

2
Це і інші питання теж: 0не числом, і воно підлягає безпідставного ін'єкції коди, спробуйте: isInteger 'a[$(ls)]'. Ооопс.
gniourf_gniourf

1
І розширення $((...))не цитується, число IFS=123може змінити його.
Ісаак
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.