Como faço para testar se uma variável é um número no Bash?

Eu simplesmente não consigo descobrir como garantir que um argumento passado para o meu script seja um número ou não.

Tudo o que eu quero fazer é algo assim:

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

Qualquer ajuda?

Uma abordagem é usar uma expressão regular, assim:

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

Se o valor não for necessariamente um inteiro, considere corrigir o regex apropriadamente; por exemplo:

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

… ou, para manipular números com um sinal:

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

Sem bashisms (funciona até no System V sh),

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

Isso rejeita strings e strings vazias contendo não-dígitos, aceitando todo o resto.

Números negativos ou de ponto flutuante precisam de algum trabalho adicional. Uma ideia é excluir - / . no primeiro padrão “ruim” e adicionar mais padrões “ruins” contendo os usos inapropriados deles ( ?*-* / *.*.* )

A solução a seguir também pode ser usada em shells básicos, como o Bourne, sem a necessidade de expressões regulares. Basicamente, qualquer operação de avaliação de valor numérico usando números diferentes resultará em um erro que será implicitamente considerado como falso no shell:

 "$var" -eq "$var" 

como em:

 #!/bin/bash var=a if [ "$var" -eq "$var" ] 2>/dev/null; then echo number else echo not a number fi 

Você também pode testar por $? o código de retorno da operação que é mais explícito:

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

O redirecionamento do erro padrão está lá para ocultar a mensagem “expressão de número inteiro esperada” que o bash imprime caso não tenhamos um número.

CAVEATS (graças aos comentários abaixo):

  • Números com pontos decimais não são identificados como “números” válidos
  • A utilização de [[ ]] vez de [ ] será sempre avaliada como true
  • A maioria dos shells não Bash sempre avaliam essa expressão como true
  • O comportamento no Bash é indocumentado e pode, portanto, mudar sem aviso
  • Se o valor include espaços após o número (por exemplo, “1 a”) produzirá erro, como bash: [[: 1 a: syntax error in expression (error token is "a")
  • Se o valor for o mesmo que var-name (por exemplo, i = “i”), produz erro, como bash: [[: i: expression recursion level exceeded (error token is "i")

Isso testa se um número é um número inteiro não negativo e é independente do shell (ou seja, sem bashisms) e usa apenas os resources internos do shell:

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

MAS ESTÁ ERRADO .
Como Jilles comentou e sugeriu em sua resposta, esta é a maneira correta de fazer isso usando padrões de shell.

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

Estou surpreso com as soluções que analisam diretamente os formatos numéricos no shell. shell não é adequado para isso, sendo uma DSL para controlar arquivos e processos. Existem analisadores de números amplos um pouco abaixo, por exemplo:

 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 } 

Altere ‘% f’ para qualquer formato específico que você precisar.

Ninguém sugeriu a correspondência de padrões estendidos do bash:

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

Eu estava olhando as respostas e … percebi que ninguém pensou em números FLOAT (com ponto)!

Usar o grep também é ótimo.
-E significa regexp estendido
-q significa quieto (não faz eco)
-qE é a combinação de ambos.

Para testar diretamente na linha de comando:

 $ 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 

Usando em um script 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 

Para corresponder apenas a inteiros, use isto:

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

Apenas um acompanhamento para @mary. Mas como não tenho representante suficiente, não posso postar isso como um comentário para esse post. De qualquer forma, aqui está o que eu usei:

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

A function retornará “1” se o argumento for um número, caso contrário, retornará “0”. Isso funciona para números inteiros e flutuantes. O uso é algo como:

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

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

Você também pode usar classs de caracteres do bash.

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

Numerics includeá espaço, o ponto decimal e “e” ou “E” para ponto flutuante.

Mas, se você especificar um número hexadecimal em estilo C, ou seja, “0xffff” ou “0XFFFF”, [[: digit:]] retornará verdadeiro. Um pouco de uma armadilha aqui, o bash permite que você faça algo como “0xAZ00” e ainda conte como um dígito (não é isso de uma estranha peculiaridade dos compiladores do GCC que permitem usar a notação 0x para bases diferentes de 16 ??? )

Você pode querer testar “0x” ou “0X” antes de testar se é um numérico se sua input for completamente não confiável, a menos que você queira aceitar números hexadecimais. Isso seria realizado por:

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

Pergunta antiga, mas eu só queria abordar minha solução. Este não requer nenhum truque de shell estranho, ou depende de algo que não tem existido para sempre.

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

Basicamente, ele apenas remove todos os dígitos da input e, se você tiver uma string de tamanho diferente de zero, não é um número.

Eu tentaria isso:

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

Nota: isto reconhece nan e inf como número.

Ainda não posso comentar, então adicionarei minha própria resposta, que é uma extensão da resposta de glenn jackman usando correspondência de padrões bash.

Minha necessidade original era identificar números e distinguir números inteiros e flutuantes. As definições das funções são deduzidas para:

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

Eu usei teste de unidade (com shUnit2) para validar meus padrões trabalhados como pretendido:

 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 } 

Notas: O padrão isFloat pode ser modificado para ser mais tolerante sobre o ponto decimal ( @(.,) ) E o símbolo E ( @(Ee) ). Meus testes de unidade testam apenas valores que são inteiros ou flutuantes, mas não qualquer input inválida.

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

Não se esqueça de include números negativos!

Eu uso expr . Ele retorna um valor diferente de zero se você tentar adicionar um zero a um valor não numérico:

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

Pode ser possível usar bc se você precisar de não-inteiros, mas eu não acredito que bc tenha o mesmo comportamento. Adicionando zero a um não-número, você recebe zero e também retorna um valor de zero. Talvez você possa combinar bc e expr . Use bc para adicionar zero a $number . Se a resposta for 0 , tente expr para verificar se o $number não é zero.

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

${i//[0-9]} substitui qualquer dígito no valor de $i por uma string vazia, veja man -P 'less +/parameter\/' bash . -z verifica se a string resultante tem comprimento zero.

Se você também quiser excluir o caso quando $i estiver vazio, você poderá usar uma dessas construções:

 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 

A maneira mais simples é verificar se ele contém caracteres não typescripts. Você substitui todos os caracteres de dígitos por nada e verifica a duração. Se há comprimento, não é um número.

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

Uma resposta clara já foi dada por @charles Dufy e outros. Uma solução bash pura estaria usando o seguinte:

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

Embora para números reais não seja obrigatório ter um número antes do ponto de radix .

Para fornecer um suporte mais completo de números flutuantes e notação científica (muitos programas em C / Fortran ou então exportarão float dessa forma), um acréscimo útil a essa linha seria o seguinte:

 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 

Assim, levando a uma maneira de diferenciar tipos de números, se você estiver procurando por um tipo específico:

 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 

Nota: Poderíamos listar os requisitos sintáticos para notação decimal e científica, sendo um deles permitir vírgula como ponto de base, bem como “.”. Então, afirmamos que deve haver apenas um ponto de radix. Pode haver dois sinais +/- em um [Ee] float. Eu aprendi mais algumas regras do trabalho de Aulu, e testei contra sequências ruins como ” ‘-‘ ‘-E-1’ ‘0-0’. Aqui estão as minhas ferramentas regex / substring / expr que parecem estar segurando:

 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 

Como eu tive que mexer com isso ultimamente e como o ataque de karttu com o teste de unidade mais. Eu revisei o código e adicionei algumas outras soluções também, teste você mesmo para ver os resultados:

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

Portanto, isNumber () inclui traços, vírgulas e notação exponencial e, portanto, retorna TRUE em inteiros & floats, onde, por outro lado, isFloat () retorna FALSE em valores inteiros e isInteger () também retorna FALSE em floats. Para sua conveniência, tudo como um forro:

 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; } 

Eu uso o seguinte (para números inteiros):

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

Eu tentei receita do ultrasawblade como parecia o mais prático para mim e não poderia fazê-lo funcionar. No final, eu inventei uma outra maneira, baseada em outras na substituição de parâmetros, desta vez com a substituição da expressão regular:

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

Ele remove todos os caracteres: class: class em $ var e verifica se ficamos com uma string vazia, significando que o original era apenas números.

O que eu gosto sobre este é o seu pequeno tamanho e flexibilidade. Nesta forma, ele só funciona para 10 números inteiros não delimitados, embora certamente você possa usar o padrão correspondente para atender a outras necessidades.

Quick & Dirty: Eu sei que não é a maneira mais elegante, mas eu geralmente adicionei um zero a ele e testei o resultado. igual a:

 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)) retornará 0 ou bomba se $ 1 NÃO for um inteiro. por exemplo:

 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 } 

NOTA: Eu acho que eu nunca pensei em checar por floats ou tipos mistos que fariam toda a bomba de script … no meu caso, eu não queria que fosse mais longe. Eu vou brincar com a solução do mrucci e com o regex do Duffy – eles parecem ser os mais robustos dentro do framework bash …

Eu encontrei uma versão bem curta:

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

    number=12345 ou number=-23234 ou number=23.167 ou number=-345.234

  • verificar numérico ou não numérico

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

  • decidir sobre novas ações com base no status de saída do item acima

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

Para pegar números negativos:

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

Você poderia usar “let” também assim:

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

Mas eu prefiro usar o operador “= ~” Bash 3+ como algumas respostas neste tópico.

I use printf as other answers mentioned, if you supply the format string “%f” or “%i” printf will do the checking for you. Easier than reinventing the checks, the syntax is simple and short and printf is ubiquitous. So its a decent choice in my opinion – you can also use the following idea to check for a range of things, its not only useful for checking numbers.

 declare -r CHECK_FLOAT="%f" declare -r CHECK_INTEGER="%i" ##  Number - Number to check ##  String - Number type to check ##  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; }

I like Alberto Zaccagni’s answer.

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

Important prerequisites: – no subshells spawned – no RE parsers invoked – most shell applications don’t use real numbers

But if $var is complex (eg an associative array access), and if the number will be a non-negative integer (most use-cases), then this is perhaps more efficient?

 if [ "$var" -ge 0 ] 2> /dev/null; then .. 
 printf '%b' "-123\nABC" | tr '[:space:]' '_' | grep -q '^-\?[[:digit:]]\+$' && echo "Integer." || echo "NOT integer." 

Remove the -\? in grep matching pattern if you don’t accept negative integer.

Following up on David W’s answer from Oct ’13, if using expr this might be better

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

If numeric, multiplied by 1 gives you the same value, (including negative numbers). Otherwise you get null which you can test for

Did the same thing here with a regular expression that test the entire part and decimals part, separated with a dot.

 re="^[0-9]*[.]{0,1}[0-9]*$" if [[ $1 =~ $re ]] then echo "is numeric" else echo "Naahh, not numeric" fi