Проверить, является ли строка допустимым целым числом


117

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

Я знаю, что это должно быть легко, но не знаю как. Я мог бы сделать это на десятке языков, но не на БАШЕ!

В своем исследовании я обнаружил следующее:

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

И там есть ответ, в котором говорится о регулярном выражении, но, насколько я знаю, эта функция доступна в C (среди прочего). Тем не менее, это выглядело как отличный ответ, поэтому я попробовал его с помощью grep, но grep не знал, что с ним делать. Я попробовал -P, что на моем поле означает рассматривать его как регулярное выражение PERL - нада. 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

Не могли бы вы проиллюстрировать, как это проще всего сделать?

Честно говоря, это, на мой взгляд, недостаток TEST. У него должен быть такой флаг

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

4
FYI: [старая совместимая test; [[это новая вещь Bash, с большим количеством операций и другими правилами цитирования. Если вы уже решили придерживаться Bash, сделайте [[это (он намного лучше); если вам нужна переносимость на другие оболочки, [[полностью избегайте .
ephemient 05

Ответы:


183
[[ $var =~ ^-?[0-9]+$ ]]
  • Значок ^указывает на начало входного шаблона.
  • -Является буквальным «-»
  • В ?означает «0 или 1 из предшествующего ( -
  • Эти +средства «1 или более из предшествующих ( [0-9]
  • Значок $указывает на конец входного шаблона

Таким образом, регулярное выражение соответствует необязательному -(в случае отрицательных чисел), за которым следует одна или несколько десятичных цифр.

Ссылки :


3
Спасибо, Игнасио, через секунду попробую. Не могли бы вы объяснить это, чтобы я немного научился? Я так понимаю, он гласит: «В начале строки (^) знак минус (-) является необязательным (?), За ним следует любое количество символов от нуля до 9 включительно» ... и что тогда может + $ имею в виду? Спасибо.
Ричард Т.

10
Эти +средства «1 или более из предшествующих», и $указывает конец входного образа. Таким образом, регулярное выражение соответствует необязательному, -за которым следует одна или несколько десятичных цифр.
Игнасио Васкес-Абрамс,

ворчит по поводу ссылки на АБС
Чарльз Даффи

Это касательный вопрос, но учтите, что при указании диапазонов символов вы можете получить странные результаты; например, [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 01

Да. Это похоже. Однако я искал однострочное решение для оператора «if». Я подумал, что мне для этого не нужно вызывать функцию. Кроме того, я вижу, что перенаправление stderr на stdout в функции. Когда я попробовал, отобразилось сообщение stderr «Ожидается целочисленное выражение», что было для меня нежелательно.
Питер Хо

Спасибо! Я бы назвал это простым и элегантным.
Эзра Нугрохо

2
Существует заметное различие между вашим решением и регулярным выражением: размер целого числа проверяется на соответствие ограничениям bash (на моем компьютере это 64 бита). Этот предел не попадает в решение regexp. Таким образом, ваше решение не сработает на количестве, строго превышающем 9223372036854775807 на 64-битных компьютерах.
vaab 06

2
Как я недавно обнаружил, есть некоторые оговорки .
Kyle Strand

28

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

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

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


4
Хотел бы я проголосовать за это один раз каждый раз, когда возвращаюсь к этому вопросу из-за обмана. Меня беспокоит то, что простое, но совместимое с POSIX решение спрятано на дне.
Адриан Фрювирт

3
Может быть, тебе стоит позаботиться о пустых строках:''|*[!0-9]*)
Никлас Питер

2
BTW: Вот этот синтаксис задокументирован: tldp.org/LDP/abs/html/string-manipulation.html
Никлас Питер

Я не особо одобряю АБС; очевидно, это также задокументировано в руководстве по Bash. В любом случае, раздел, на который вы ссылаетесь, не описывает эту конкретную конструкцию, а скорее, например, ответ @Nortally.
tripleee

@tripleee Связанный документ описывает конструкцию для удаления префикса строки из переменной, используемой в строке case. Он находится внизу страницы, но там нет якорей, поэтому я не мог напрямую ссылаться на него, см. Раздел «Удаление подстроки»
Никлас Питер

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

Для переносимости на pre-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 любят ~~больше, но здесь это не влияет.) Я полагаю, вы могли бы рассматривать это как своего рода примерное равенство ...
ephemient

Отличный пост И редактировать! Мне очень приятно объяснить, что это значит. Хотел бы я отметить ваши сообщения и сообщения Игнасио как правильный ответ. - нахмуриться - Вы оба молодцы. Но поскольку у вас вдвое большая репутация, я отдаю ее Игнасио - надеюсь, вы понимаете! -smile-
Ричард Т

4

Вот еще один вариант (только с использованием встроенной команды test и ее кода возврата):

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))"фигурные скобки нигде в вашем скрипте не нужны.
Приостановлено до дальнейшего уведомления.

Я ожидал, что это также будет обрабатывать числа в базовой нотации Bash как действительные целые числа (которые, конечно, по определению таковы; но это может не совпадать с вашим), но test, похоже, не поддерживает это. [[хотя делает. [[ 16#aa -eq 16#aa ]] && echo integerпечатает «целое число».
tripleee

Обратите внимание, что [[этот метод возвращает ложные срабатывания; например, [[ 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 Не целое число

Привет, Деннис! Спасибо, что познакомили меня с синтаксисом справа от match = выше. Я никогда раньше не замечал такого синтаксиса типов. Я узнаю кое-что из синтаксиса tr (утилита, которую я еще не совсем освоил, но иногда ковыряюсь); где я могу прочитать о таком синтаксисе? (т.е. как называется этот тип вещей?) Спасибо.
Ричард Т.

Вы можете посмотреть на странице человека Bash в разделе «Параметры расширения» для получения информации о ${var//string}и ${var#string}и в разделе под названием «Pattern Matching» для [^ [: цифры:]] `(который также покрыт man 7 regex).
Приостановлено до дальнейшего уведомления.

1
match=${match#0*}вовсе не полосы ведущих нулей, то полосы не более одного нуля. Используя расширение, это может быть достигнуто только с extglobпомощью via match=${match##+(0)}.
Адриан Фрювирт

Разве 9 или 09 не целое число?
Mike Q

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

2

Для меня самым простым решением было использовать переменную внутри (())выражения, например:

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

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

Как указано в комментариях, это может привести к атаке выполнения кода: (( ))оператор VARвыполняет оценку , как указано в Arithmetic Evaluationразделе справочной страницы bash (1) . Следовательно, вы не должны использовать эту технику, когда источник содержимого VARнеизвестен (и, конечно, вы не должны использовать ЛЮБУЮ другую форму расширения переменных).


Вы можете пойти даже прощеif (( var )); then echo "$var is an int."; fi
Аарон Р.

2
Но это также вернет истину для отрицательных целых чисел, @aaronr, а не то, что искал OP.
Требор Грубый

2
Это опасно, см .: n = 1; var = "n"; если ((var)); затем echo "$ var is an int."; Fi
Ярно

2
Это очень плохая идея , и при условии произвольного кода: попробуйте сами: VAR='a[$(ls)]'; if ((VAR > 0)); then echo "$VAR is a positive integer"; fi. На данный момент вы рады, что я не ввел какую-то злую команду вместо ls. Поскольку OP упоминает ввод пользователя , я действительно надеюсь, что вы не используете его с вводом пользователя в производственном коде!
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"... хотя это в основном дублирует ответ Денниса Уильямсона
tripleee

Спасибо! Единственный ответ, который здесь действительно работает!
пользователь

Тихая альтернатива: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]...?
Tripleee

0

Для смеха я примерно быстро разработал набор функций для этого (is_string, is_int, is_float, is alpha string или другие), но есть более эффективные (меньше кода) способы сделать это:

#!/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.