Перевірте, чи рядок є дійсним цілим числом


117

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

Я знаю, що це повинно бути легко, але я не знаю як. Я міг би це зробити на десятках мов, але не БАШ!

У своєму дослідженні я виявив це:

Регулярне вираження, щоб перевірити, чи складається рядок з дійсного дійсного числа в базі 10

І тут є відповідь, що говорить про регулярне вираження, але наскільки я знаю, це функція, доступна в C (серед інших). Тим не менш, це мало те, що виглядало як чудова відповідь, тому я спробував це з grep, але grep не знав, що з цим робити. Я спробував -П, що на моїй коробці означає трактувати це як PERL regexp - nada. Dash E (-E) також не працював. І ні -F.

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

snafu=$(echo "$2" | grep -E "/^[-+]?(?:\.[0-9]+|(?:0|[1-9][0-9]*)(?:\.[0-9]*)?)$/")
if [ -z "$snafu" ] ;
then
   echo "Not an integer - nothing back from the grep"
else
   echo "Integer."
fi

Хтось, будь ласка, проілюструє, як це найлегше зробити?

Відверто кажучи, це, на мою думку, короткочасне тестування. Він повинен мати такий прапор

if [ -I "string" ] ;
then
   echo "String is a valid integer."
else
   echo "String is not a valid integer."
fi

4
FYI: [стара сумісна test; [[- це нова річ Баша, що має більше операцій та різних правил цитування. Якщо ви вже вирішили дотримуватися Баша, продовжуйте [[(це дійсно набагато приємніше); якщо вам потрібна переносимість інших снарядів, уникайте їх [[повністю.
ефемієнт

Відповіді:


183
[[ $var =~ ^-?[0-9]+$ ]]
  • ^Вказує на початок паттерна введення
  • Це -буквальне "-"
  • В ?означає «0 або 1 з попереднього ( -
  • Ці +кошти «1 або більше з попередніх ( [0-9]
  • $Вказує на кінець шаблону введення

Таким чином, регулярний вирівнювання відповідає необов'язковій -(для випадку від'ємних чисел) з наступною однією або кількома десятковими цифрами.

Список літератури :


3
Спасибі Ігнасіо, я спробую це за секунду. Ви б не проти пояснити це, щоб я міг трохи навчитися? Я вважаю, що "На початку рядка (^) знак мінус (-) необов'язковий (?), А потім будь-яка кількість символів між нулем і 9, включно" ... і що тоді може бути + $ означає? Дякую.
Річард Т

10
Засіб +"1 або більше попереднього", а також $вказує кінець вхідного шаблону. Таким чином, регулярний вирівнювання відповідає необов'язковій -послідовності з однією або кількома десятковими цифрами.
Ігнасіо Васкес-Абрамс

бурчить повторно: посилання ABS
Чарльз Даффі

Це дотична, але зауважте, що вказуючи діапазони символів, ви можете отримати незвичайні результати; наприклад, [A-z]не тільки дасть вам A-Zі , a-zа й \ , [, ], ^, _, і `.
Doktor J

Крім того, на основі зібрання символів ( див. Це пов'язане питання / відповідь ) щось подібне d[g-i]{2}може закінчитися не тільки збігом, digале і dishв порівнянні, запропонованому цією відповіддю (де shдиграф вважається одним символом, зіставленим після h).
Doktor J

61

Нічого ... тут дуже багато хороших рішень !! З усіх вищезазначених рішень я погоджуюся з @nortally, що використовувати -eqодин вкладиш - це найкрутіше.

Я запускаю GNU bash, версія 4.1.5(Debian). Я також перевірив це на ksh (SunSO 5.10).

Ось моя версія перевірки, чи $1є ціле число чи ні:

if [ "$1" -eq "$1" ] 2>/dev/null
then
    echo "$1 is an integer !!"
else
    echo "ERROR: first parameter must be an integer."
    echo $USAGE
    exit 1
fi

Цей підхід також враховує негативні числа, які в деяких інших рішеннях матимуть помилковий негативний результат, і він дозволить префіксу "+" (наприклад, +30), що, очевидно, є цілим числом.

Результати:

$ int_check.sh 123
123 is an integer !!

$ int_check.sh 123+
ERROR: first parameter must be an integer.

$ int_check.sh -123
-123 is an integer !!

$ int_check.sh +30
+30 is an integer !!

$ int_check.sh -123c
ERROR: first parameter must be an integer.

$ int_check.sh 123c
ERROR: first parameter must be an integer.

$ int_check.sh c123
ERROR: first parameter must be an integer.

Рішення, яке надав Ігнасіо Васкес-Абрамс, було також дуже акуратним (якщо вам подобається регулярний вираз) після його пояснення. Однак він не обробляє додаткові числа з +префіксом, але його можна легко виправити як нижче:

[[ $var =~ ^[-+]?[0-9]+$ ]]

Приємно! Досить схоже на це , хоча.
devnull

Так. Це схоже. Однак я шукав рішення для одного вкладиша для заяви "якщо". Я думав, що для цього мені не потрібно викликати функцію. Також я бачу, що перенаправлення stderr на stdout у функції. Коли я спробував, на дисплеї з'явилося більш жорстке повідомлення "очікуваний цілий вираз", яке для мене було небажаним.
Пітер Хо

Дякую! Я б назвав це легким та елегантним.
Езра Нугрохо

2
Існує помітна відмінність між вашим рішенням і регулярним виразом: розмір цілого числа перевіряється до меж bash (на моєму комп'ютері це 64bit). Ця межа не досягає рішення регулярного виведення. Таким чином, ваше рішення вийде з ладу на номер, строго більший за 9223372036854775807 на 64-бітних комп’ютерах.
vaab

2
Як я нещодавно виявив, є деякі застереження .
Кайл Странд

28

Тут пізній вечірка. Я надзвичайно здивований, що жодна з відповідей не згадує найпростіше, найшвидше, найпотужніше рішення; caseзаяву.

case ${variable#[-+]} in
  *[!0-9]* | '') echo Not a number ;;
  * ) echo Valid number ;;
esac

Обрізання будь-якої ознаки перед порівнянням відчуває себе трохи злому, але це робить вираз для висловлювання справи набагато простішим.


4
Я б хотів, щоб я міг підкреслити це кожен раз, коли я повертаюся до цього питання через дупи. Це переробляє мою передачу, що просте, але сумісне з POSIX рішенням закопане внизу.
Адріан Фрюхвітрт

3
Можливо, вам слід подбати про порожні рядки:''|*[!0-9]*)
Ніклас Петро

2
BTW: Ось цей синтаксис задокументовано: tldp.org/LDP/abs/html/string-manipulation.html
Niklas Peter

Я особливо не потураю АБС; це очевидно також задокументовано в посібнику Баша. У будь-якому випадку розділ, до якого ви посилаєтесь, не описує цю конкретну конструкцію, а, наприклад, відповідь @ Nortally.
трійчатка

@tripleee Пов'язаний документ описує конструкцію для видалення рядкового префіксу зі змінної, що використовується у рядку регістру. Він знаходиться внизу сторінки, але прив’язок немає, тому я не зміг безпосередньо посилання на нього, дивіться розділ "Видалення підрядків"
Niklas Peter

10

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

Моє власне рішення полягало у використанні розширення параметрів, щоб викинути всі цифри і подивитися, чи не залишилось нічого. (Я все ще використовую 3.0, не використовував [[або exprраніше, але радий їх зустріти.)

if [ "${INPUT_STRING//[0-9]}" = "" ]; then
  # yes, natural number
else
  # no, has non-numeral chars
fi

4
Це можна вдосконалити за допомогою, [ -z "${INPUT_STRING//[0-9]}" ]але дуже приємного рішення!
ShellFish

як щодо негативних ознак?
скоттісей

-eqРішення має деякі проблеми; дивіться тут: stackoverflow.com/a/808740/1858225
Кайл Странд

Порожній INPUT_STRING вважається числом, тому не вдається для мого випадку
Manwe

9

Для переносимості до Bash 3.1 (коли =~тест був введений), використовуйте expr.

if expr "$string" : '-\?[0-9]\+$' >/dev/null
then
  echo "String is a valid integer."
else
  echo "String is not a valid integer."
fi

expr STRING : REGEXшукає прив’язаний REGEX на початку STRING, повторюючи першу групу (або тривалість відповідності, якщо такої немає) та повертає успіх / невдачу. Це старий синтаксис регулярних виразів, звідси і надлишок \. -\?означає "можливо -", [0-9]\+означає "одну або кілька цифр" і $означає "кінець рядка".

Bash також підтримує розширені кулі, хоча я не пам'ятаю, від якої версії далі.

shopt -s extglob
case "$string" of
    @(-|)[0-9]*([0-9]))
        echo "String is a valid integer." ;;
    *)
        echo "String is not a valid integer." ;;
esac

# equivalently, [[ $string = @(-|)[0-9]*([0-9])) ]]

@(-|)означає " -або нічого", [0-9]означає "цифра" і *([0-9])означає "нуль або більше цифр".


Дякую вам, благодійник, дуже зобов’язаний. Я ніколи не бачив синтаксис = ~ - і досі не маю уявлення, що це має означати - приблизно рівний ?! ... Я ніколи не порушувалися до програми в BASH , але це необхідно кілька разів!
Річард Т

В awk, ~був оператором "матчу регулярного вибору". У Perl (як скопійовано з C) ~вже використовувались для "бітового доповнення", тому вони використовували =~. Ці пізніші позначення скопійовані на кілька інших мов. (Perl 5.10 і Perl 6 люблять ~~більше, але це не впливає на це.) Я гадаю, ви могли б
сприймати

Відмінна публікація І редагування! Я дуже вдячний пояснити, що це означає. Я б хотів, щоб я міг позначити як ваші, так і посади Ігнасіо як правильну відповідь. -нахмурились - Ви, хлопці, чудові. Але оскільки у вас є подвійна репутація, яку він робить, я віддаю це Ігнасіо - сподіваюся, ви зрозуміли! -smile-
Річард Т

4

Ось ще один прийом для цього (лише за допомогою тестової вбудованої команди та її коду повернення):

function is_int() { return $(test "$@" -eq "$@" > /dev/null 2>&1); } 

input="-123"

if $(is_int "${input}");
then
   echo "Input: ${input}"
   echo "Integer: $[${input}]"
else
   echo "Not an integer: ${input}"
fi

1
Це не обов'язково використовувати $()з if. Це працює: if is_int "$input". Також $[]форма застаріла. Використовуйте $(())замість цього. Зсередини або знак долара може бути опущений: echo "Integer: $((input))"фігурні дужки не потрібні ніде у вашому сценарії.
Призупинено до подальшого повідомлення.

Я би очікував, що це також обробляє числа в базовій позначці Баша як дійсні цілі числа (які, звичайно, за певним визначенням вони є, але це може не погодитися з вашими), але test, схоже, це не підтримує. [[робить, хоча. [[ 16#aa -eq 16#aa ]] && echo integerдрукує "ціле число".
трійка

Зауважте, що [[повертає помилкові позитиви для цього методу; наприклад, [[ f -eq f ]]вдається. Отже, він повинен використовувати testабо [.
спінап

3

Можна викреслити нецифрові цифри і зробити порівняння. Ось демонстраційний сценарій:

for num in "44" "-44" "44-" "4-4" "a4" "4a" ".4" "4.4" "-4.4" "09"
do
    match=${num//[^[:digit:]]}    # strip non-digits
    match=${match#0*}             # strip leading zeros
    echo -en "$num\t$match\t"
    case $num in
        $match|-$match)    echo "Integer";;
                     *)    echo "Not integer";;
    esac
done

Ось як виглядає тестовий вихід:

44 44 Цілий ряд
-44 44 Ціле число
44–44 Не ціле число
4-4 44 Не ціле число
a4 4 Не ціле число
4a 4 Не ціле число
.4 4 Не ціле число
4.4 44 Не ціле число
-4,4 44 Не ціле число
09 9 Не ціле число

Привіт Деннісе! Дякую, що ти познайомив мене з синтаксисом праворуч від відповідності = вище. Я ніколи раніше не помічав такого типу синтаксису. Я розпізнаю частину синтаксису з tr (утиліта, яку я ще не зовсім засвоїв, але іноді пробиваю шлях); де я можу прочитати такий синтаксис? (тобто, як називається цей тип речі?) Дякую
Річард Т

Ви можете подивитися на сторінці "Bash man" у розділі "Розширення параметрів", щоб отримати інформацію про ${var//string}та ${var#string}та у розділі "Зіставлення шаблонів" для [^ [: цифра:]] `(яка також розглянута в man 7 regex).
Призупинено до подальшого повідомлення.

1
match=${match#0*}зовсім НЕ шпальти провідних нулів, то смуги не більше одного нуля. Використовуючи розширення, це можна досягти, лише використовуючи extglobvia match=${match##+(0)}.
Адріан Фрюхвірт

Чи не 9 чи 09 ціле число?
Майк Q

@MikeQ: 09не є цілим числом, якщо ви вважаєте, що ціле число не має провідних нулів. Тест полягає в тому, чи вхід ( 09) дорівнює санізованій версії ( 9- цілому числу) і чи ні.
Призупинено до подальшого повідомлення.

2

Для мене найпростішим рішенням було використання змінної всередині (())виразу:

if ((VAR > 0))
then
  echo "$VAR is a positive integer."
fi

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

Як зазначено в коментарях, це може стати причиною атаки виконання коду: (( ))Оператор оцінює VAR, як зазначено в Arithmetic Evaluationрозділі man-сторінки bash (1) . Тому ви не повинні використовувати цю техніку, коли джерело вмісту VARне визначене (і, звичайно, не слід використовувати будь-яку іншу форму змінного розширення).


Можна навіть піти простіше зif (( var )); then echo "$var is an int."; fi
Аарон Р.

2
Але це також поверне справжнє значення для від'ємних цілих чисел, @aaronr, а не те, що шукала ОП.
Trebor Rude

2
Це небезпечно, див .: n = 1; var = "n"; if ((var)); тоді відлуння "$ var - це int."; fi
jarno

2
Це дуже погана ідея , і при умови довільного коду: спробуйте самі: VAR='a[$(ls)]'; if ((VAR > 0)); then echo "$VAR is a positive integer"; fi. У цей момент ви раді, що замість цього я не ввів якусь злу команду ls. Оскільки ОП згадує введення користувачів , я дуже сподіваюся, що ви не використовуєте це з введенням користувача у виробничий код!
gniourf_gniourf

Це не працює, якщо рядок містить деякі цифри, такі як:agent007
brablc

1

або з sed:

   test -z $(echo "2000" | sed s/[0-9]//g) && echo "integer" || echo "no integer"
   # integer

   test -z $(echo "ab12" | sed s/[0-9]//g) && echo "integer" || echo "no integer"
   # no integer

У Bash та інших снарядів "Bourne plus" ви можете уникнути заміни команд та зовнішньої команди за допомогою test -z "${string//[0-9]/}" && echo "integer" || echo "no integer"... хоча це в основному дублює відповідь Денніса Вільямсона
триплея

Дякую! Єдина відповідь, яка насправді працює тут!
користувач

Мовчазна альтернатива:if [[ -n "$(printf "%s" "${2}" | sed s/[0-9]//g)" ]]; then
користувач

0

Додавання до відповіді Ігнасіо Васкеса-Абрамса. Це дозволить знаку + передувати цілому числу, і це дозволить будь-якій кількості нулів у вигляді десяткових знаків. Наприклад, це дозволить +45.00000000 вважати цілим числом.
Однак 1 долар повинен бути відформатований, щоб він містив десяткову точку. 45 не вважається цілим числом, але 45.0 - це.

if [[ $1 =~ ^-?[0-9]+.?[0]+$ ]]; then
    echo "yes, this is an integer"
elif [[ $1 =~ ^\+?[0-9]+.?[0]+$ ]]; then
    echo "yes, this is an integer"
else
    echo "no, this is not an integer"
fi

Чи є причина, що ви використовуєте два різних регулярних вирази для додатних і від’ємних чисел, а не ^[-+]?[0-9]…?
трійчатка

0

Для сміху я приблизно просто розробив набір функцій для цього (is_string, is_int, is_float, альфа-рядок чи інші), але є більш ефективні (менш код) способи зробити це:

#!/bin/bash

function strindex() {
    x="${1%%$2*}"
    if [[ "$x" = "$1" ]] ;then
        true
    else
        if [ "${#x}" -gt 0 ] ;then
            false
        else
            true
        fi
    fi
}

function is_int() {
    if is_empty "${1}" ;then
        false
        return
    fi
    tmp=$(echo "${1}" | sed 's/[^0-9]*//g')
    if [[ $tmp == "${1}" ]] || [[ "-${tmp}" == "${1}" ]] ; then
        #echo "INT (${1}) tmp=$tmp"
        true
    else
        #echo "NOT INT (${1}) tmp=$tmp"
        false
    fi
}

function is_float() {
    if is_empty "${1}" ;then
        false
        return
    fi
    if ! strindex "${1}" "-" ; then
        false
        return
    fi
    tmp=$(echo "${1}" | sed 's/[^a-z. ]*//g')
    if [[ $tmp =~ "." ]] ; then
        #echo "FLOAT  (${1}) tmp=$tmp"
        true
    else
        #echo "NOT FLOAT  (${1}) tmp=$tmp"
        false
    fi
}

function is_strict_string() {
    if is_empty "${1}" ;then
        false
        return
    fi
    if [[ "${1}" =~ ^[A-Za-z]+$ ]]; then
        #echo "STRICT STRING (${1})"
        true
    else
        #echo "NOT STRICT STRING (${1})"
        false
    fi
}

function is_string() {
    if is_empty "${1}" || is_int "${1}" || is_float "${1}" || is_strict_string "${1}" ;then
        false
        return
    fi
    if [ ! -z "${1}" ] ;then
        true
        return
    fi
    false
}
function is_empty() {
    if [ -z "${1// }" ] ;then
        true
    else
        false
    fi
}

Провівши тут кілька тестів, я визначив, що -44 - це int, але 44 - не і т.д. ..:

for num in "44" "-44" "44-" "4-4" "a4" "4a" ".4" "4.4" "-4.4" "09" "hello" "h3llo!" "!!" " " "" ; do
    if is_int "$num" ;then
        echo "INT = $num"

    elif is_float "$num" ;then
        echo "FLOAT = $num"

    elif is_string "$num" ; then
        echo "STRING = $num"

    elif is_strict_string "$num" ; then
        echo "STRICT STRING = $num"
    else
        echo "OTHER = $num"
    fi
done

Вихід:

INT = 44
INT = -44
STRING = 44-
STRING = 4-4
STRING = a4
STRING = 4a
FLOAT = .4
FLOAT = 4.4
FLOAT = -4.4
INT = 09
STRICT STRING = hello
STRING = h3llo!
STRING = !!
OTHER =  
OTHER = 

ПРИМІТКА. Ведучі 0 можуть зробити щось інше при додаванні таких цифр, як восьмери вісімки, тому було б краще зняти їх, якщо ви збираєтесь трактувати '09' як int (що я роблю) (наприклад, expr 09 + 0або смужку з sed)

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.