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


599

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 como isto:

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

Qualquer ajuda?


17
Como um aparte - a test && echo "foo" && exit 0 || echo "bar" && exit 1abordagem que você está usando pode ter alguns efeitos colaterais indesejados - se o eco falhar (talvez a saída seja para um FD fechado), exit 0ele será ignorado e o código tentará echo "bar". Se também falhar, a &&condição falhará e nem será executada exit 1! Usar ifdeclarações reais em vez de &&/ ||é menos propenso a efeitos colaterais inesperados.
Charles Duffy

@CharlesDuffy Esse é o tipo de pensamento realmente inteligente que a maioria das pessoas só consegue quando precisa rastrear insetos peludos ...! Eu nunca pensei que eco pudesse retornar falha.
Camilo Martin

6
Um pouco atrasado para a festa, mas eu sei sobre os perigos sobre os quais Charles escreveu, pois eu também tive que passar por eles há algum tempo. Então, aqui está uma linha 100% à prova de idiotas (e bem legível) para você: [[ $1 =~ "^[0-9]+$" ]] && { echo "number"; exit 0; } || { echo "not a number"; exit 1; }Os colchetes indicam que as coisas NÃO devem ser executadas em um subshell (o que definitivamente seria assim com os ()parênteses usados). Advertência: nunca perca o ponto e vírgula final . Caso contrário, você pode causar bashpara imprimir o mais feio (e mais inútil) mensagens de erro ...
SyntaxError

5
Não funciona no Ubuntu, a menos que você não remova as aspas. Por isso, deve ser apenas[[ 12345 =~ ^[0-9]+$ ]] && echo OKKK || echo NOOO
Treviño

4
Você precisará ser mais específico sobre o que você quer dizer com "número" . Um inteiro? Um número de ponto fixo? Notação científica ("e")? Existe um intervalo necessário (por exemplo, um valor não assinado de 64 bits) ou você permite qualquer número que possa ser gravado?
Toby Speight

Respostas:


803

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 número inteiro, considere alterar o regex adequadamente; por exemplo:

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

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

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

7
+1 para essa abordagem, mas tome cuidado com decimais, realizando este teste com, por exemplo, erro "1,0" ou "1,0" imprime ": Não é um número".
Sourcerebels

15
Acho que o '' exec> & 2; eco ... '' bastante bobo. Apenas '' eco ...> & 2 ''
lhunath 2/09/09

5
@Ben você realmente quer lidar com mais de um sinal de menos? Eu faria isso em ^-?vez de, a ^-*menos que você esteja realmente fazendo o trabalho para lidar com várias inversões corretamente.
Charles Duffy

4
@SandraSchlichting Faz com que toda a saída futura vá para o stderr. Não é realmente um ponto para isso aqui, onde há apenas um eco, mas é um hábito que costumo me envolver nos casos em que as mensagens de erro abrangem várias linhas.
Charles Duffy

29
Não sei por que a expressão regular deve ser salva em uma variável, mas se for por uma questão de compatibilidade, não acho que seja necessário. Você poderia simplesmente aplicar a expressão diretamente: [[ $yournumber =~ ^[0-9]+$ ]].
konsolebox

284

Sem bashisms (funciona mesmo no System V sh),

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

Isso rejeita cadeias vazias e cadeias que não sejam dígitos, aceitando todo o resto.

Os números negativos ou de ponto flutuante precisam de algum trabalho adicional. Uma idéia é excluir -/ .no primeiro padrão "ruim" e adicionar mais padrões "ruins" contendo os usos inadequados deles ( ?*-*/ *.*.*)


20
+1 - este é o caminho idiomático e portátil de volta ao shell Bourne original e possui suporte interno para curingas no estilo glob. Se você vem de outra linguagem de programação, parece estranha, mas é muito mais elegante do que lidar com a fragilidade de várias questões citando e para trás sem fim / problemas de compatibilidade de lado, comif test ...
tripleee

6
Você pode alterar a primeira linha para ${string#-}(que não funciona em shells Bourne antigos, mas funciona em qualquer shell POSIX) para aceitar números inteiros negativos.
Gilles 'SO- stop be evil'

4
Além disso, é fácil estender para flutuadores - basta adicionar '.' | *.*.*aos padrões não permitidos e adicionar pontos aos caracteres permitidos. Da mesma forma, você pode permitir um sinal opcional antes, embora eu prefira case ${string#[-+]}simplesmente ignorar o sinal.
tripleee

Veja isso para manipular números inteiros assinados: stackoverflow.com/a/18620446/2013911
Niklas Peter

2
@Dor As aspas não são necessárias, pois o comando case não realiza divisão de palavras nem geração de nome de caminho nessa palavra. (No entanto, as expansões nos padrões de casos pode ser necessário citar uma vez que determina se os caracteres padrão de correspondência são literais ou especial.)
Jilles

193

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

"$var" -eq "$var"

como em:

#!/bin/bash

var=a

if [ -n "$var" ] && [ "$var" -eq "$var" ] 2>/dev/null; then
  echo number
else
  echo not a number
fi

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

[ -n "$var" ] && [ "$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 inteira esperada" que o bash é impresso caso não tenhamos um número.

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

  • Números com casas decimais não são identificados como "números" válidos
  • Usar em [[ ]]vez de [ ]sempre avaliará setrue
  • A maioria dos shells não Bash sempre avaliará essa expressão como true
  • O comportamento no Bash não é documentado e, portanto, pode ser alterado sem aviso
  • Se o valor incluir 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"), gera erro, como bash: [[: i: expression recursion level exceeded (error token is "i")

8
Eu ainda recomendaria isso (mas com as variáveis ​​citadas para permitir seqüências de caracteres vazias), pois o resultado é garantido como utilizável como um número no Bash, não importa o quê.
l0b0

21
Tome cuidado para usar colchetes simples; [[ a -eq a ]]avalia a verdade (ambos os argumentos são convertidos para zero)
Tgr

3
Muito agradável! Observe que isso funciona apenas para um número inteiro, não para qualquer número. Eu precisava para verificar se há um único argumento que deve ser um inteiro, então isso funcionou bem:if ! [ $# -eq 1 -o "$1" -eq "$1" ] 2>/dev/null; then
haridsv

6
Eu recomendaria fortemente contra esse método por causa do número não insignificante de shells cujo [builtin avaliará os argumentos como aritméticos. Isso é verdade tanto no ksh93 quanto no mksh. Além disso, como as duas matrizes de suporte, há uma oportunidade fácil para a injeção de código. Use uma correspondência de padrões.
Ormaaj 8/10/14

3
@AlbertoZaccagni, nas versões atuais do bash, esses valores são interpretados com regras de contexto numérico apenas para, [[ ]]mas não para [ ]. Dito isto, esse comportamento não é especificado pelo padrão POSIX testda documentação do bash; versões futuras do bash podem modificar o comportamento para corresponder ao ksh sem quebrar nenhuma promessa comportamental documentada, portanto, não é garantido que confiar no comportamento atual persistente.
Charles Duffy

43

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

INCORRETA.

Como esta primeira resposta (abaixo) permite números inteiros com caracteres, desde que a primeira não seja a primeira na variável.

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

CORRETO .

Como Jilles comentou e sugeriu em sua resposta, esta é a maneira correta de fazê-lo usando padrões de shell.

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

5
Isso não funciona corretamente, ele aceita qualquer sequência que comece com um dígito. Observe que WORD em $ {VAR ## WORD} e similar é um padrão de shell, não uma expressão regular.
Jilles

2
Você pode traduzir essa expressão para o inglês, por favor? Eu realmente quero usá-lo, mas não o entendo o suficiente para confiar nele, mesmo depois de ler a página de manual do bash.
CivFan

2
*[!0-9]*é um padrão que corresponde a todas as cadeias com pelo menos 1 caractere não dígito. ${num##*[!0-9]*}é uma "expansão de parâmetro" em que pegamos o conteúdo da numvariável e removemos a string mais longa que corresponde ao padrão. Se o resultado da expansão do parâmetro não estiver vazio ( ! [ -z ${...} ]), é um número, pois não contém nenhum caractere que não seja um dígito.
Mrucci 26/05

Infelizmente, isso falha se houver algum dígito no argumento, mesmo que não seja um número válido. Por exemplo "exam1ple" ou "a2b".
precisa saber é o seguinte

Ambos falham com122s :-(
Hastur

40

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

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

5
Glenn, eu removo shopt -s extglobda sua postagem (que eu votei, é uma das minhas respostas favoritas aqui), já que em Construções Condicionais você pode ler: Quando os operadores ==e !=são usados, a cadeia à direita do operador é considerada um padrão e corresponde de acordo com as regras descritas abaixo em Correspondência de padrões , como se a extglobopção shell estivesse ativada. Espero que você não se importe!
gniourf_gniourf

Em tais contextos, você não precisa shopt extglob... isso é uma coisa boa de saber!
gniourf_gniourf

Funciona bem para números inteiros simples.

2
@Jdamian: você está certo, isso foi adicionado ao Bash 4.1 (que foi lançado no final de 2009… O Bash 3.2 foi lançado em 2006… agora é um software antigo, desculpe por quem está preso no passado). Além disso, você poderia argumentar que os extglobs foram introduzidos na versão 2.02 (lançada em 1998) e não funcionam nas versões <2.02 ... Agora, seu comentário aqui servirá como uma advertência em relação às versões mais antigas.
gniourf_gniourf

1
Variáveis ​​dentro [[...]]não estão sujeitas a divisão de palavras ou expansão glob.
glenn jackman 14/01

26

Estou surpreso com as soluções que analisam diretamente os formatos numéricos no shell. O shell não é adequado para isso, sendo uma DSL para controlar arquivos e processos. Existem amplos analisadores de números um pouco mais 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.


4
isnumber(){ printf '%f' "$1" &>/dev/null && echo "this is a number" || echo "not a number"; }
Gilles Quenot

4
@sputnick sua versão quebra a semântica inerente (e útil) do valor de retorno da função original. Então, em vez disso, simplesmente deixe a função como está e use-a:isnumber 23 && echo "this is a number" || echo "not a number"
michael

4
Isso também não deveria ter 2>/dev/null, para que isnumber "foo"não polua o stderr?
Gioele

4
Chamar os shells modernos como bash de "DSL para controlar arquivos e processos" é ignorar que eles são usados ​​para muito mais do que isso - algumas distros criaram gerenciadores de pacotes e interfaces da Web inteiros (por mais feio que isso possa ser). Os arquivos em lote se encaixam na sua descrição, no entanto, é difícil definir uma variável.
Camilo Martin

7
É engraçado que você esteja tentando ser inteligente, copiando alguns idiomas de outros idiomas. Infelizmente isso não funciona com conchas. Os shells são muito especiais e, sem um conhecimento sólido sobre eles, é provável que você escreva um código quebrado. Seu código está quebrado: isnumber "'a"retornará verdadeiro. Isso está documentado na especificação POSIX, onde você lerá: Se o caractere principal for uma aspas simples ou aspas duplas, o valor será o valor numérico no conjunto de códigos subjacente do caractere após a aspas simples ou aspas duplas. .
gniourf_gniourf

16

Eu estava olhando as respostas e ... percebi que ninguém pensava nos números do FLOAT (com ponto)!

Usar grep também é ótimo.
-E significa regexp estendido
-q significa quiet (não ecoa)
-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 números inteiros, use este:

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

As soluções usando awk por triple_r e tripleee funcionam com carros alegóricos.
27617 Ken Jackson

Obrigado por este e muito bom ponto! Porque a questão é realmente como verificar se é um número e não apenas um número inteiro.
Tanasis

Agradeço-lhe também Tanasis! Vamos nos ajudar sempre.
Sergio Abreu

12

Apenas um acompanhamento para @mary. Mas como não tenho representante suficiente, não pude postar isso como um comentário nesse post. Enfim, aqui está o que eu usei:

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

A função retornará "1" se o argumento for um número, caso contrário, retornará "0". Isso funciona tanto para números inteiros quanto para flutuadores. 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

4
Imprimir um número é menos útil do que definir um código de saída. 'BEGIN { exit(1-(a==a+0)) }'é um pouco difícil de grok, mas pode ser usado em uma função que retorna true ou false como[ , grep -q, etc.
tripleee

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

${i//[0-9]}substitui qualquer dígito no valor de $ipor uma sequência vazia, consulte man -P 'less +/parameter\/' bash. -zverifica se a sequência resultante tem comprimento zero.

se você também quiser excluir o caso quando $iestiver vazio, poderá usar uma destas 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

Polegares para cima, especialmente para a man -P 'less +/parameter\/' bashparte. Aprendendo algo novo todo dia. :)
David

@sjas Você pode facilmente adicionar \-expressões regulares para resolver o problema. Use [0-9\-\.\+]para contabilizar carros alegóricos e números assinados.
User2683246

@sjas ok, minha culpa
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

Para o meu problema, eu só precisava garantir que um usuário não inserisse acidentalmente algum texto, portanto, tentei mantê-lo simples e legível

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

De acordo com a página de manual, isso praticamente faz o que eu quero

Se o valor da expressão for diferente de zero, o status de retorno será 0

Para evitar mensagens de erro desagradáveis ​​para seqüências de caracteres que "podem ser números", ignoro a saída de erro

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

Isso é mais parecido com o isInteger. Mas muito obrigado!
Naheel 30/04

8

Pergunta antiga, mas eu só queria aderir à minha solução. Este não requer truques estranhos de concha ou depende de algo que não existe há sempre.

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

Basicamente, ele remove todos os dígitos da entrada e, se você ficar com uma sequência de comprimento diferente de zero, não seria um número.


Isso falha para um vazio var.
gniourf_gniourf

Ou para variáveis ​​com novas linhas à direita ou algo assim $'0\n\n\n1\n\n\n2\n\n\n3\n'.
gniourf_gniourf

7

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

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

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

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

Eu usei o teste de unidade (com shUnit2) para validar que meus padrões funcionavam 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 com o ponto decimal ( @(.,)) e o símbolo E ( @(Ee)). Meus testes de unidade testam apenas valores inteiros ou flutuantes, mas nenhuma entrada inválida.


Sinto muito, não entendi como a função de edição deve ser usada.
3ronco 08/09/16

6

Eu tentaria o seguinte:

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

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


2
quer duplicar, ou talvez mais adequado como um comentário para, a resposta de pixelbeat (usando %fé provavelmente melhor de qualquer maneira)
michael

3
Em vez de verificar o código de status anterior, por que não colocá-lo por ifsi só? Isso é o que iffaz ...if printf "%g" "$var" &> /dev/null; then ...
Camilo Martin

3
Isso tem outras ressalvas. Ele validará a string vazia e as strings like 'a.
22715

6

Uma resposta clara já foi dada por @charles Dufy e outros. Uma solução pura do bash usaria 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 raiz .

Para fornecer um suporte mais completo aos números flutuantes e à notação científica (muitos programas em C / Fortran, ou então exportarão float dessa maneira), uma adição útil a esta linha seria a 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, você pode diferenciar tipos de número, se 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 para permitir vírgula como ponto de raiz, bem como ".". Afirmaríamos então que deve haver apenas um desses pontos de raiz. Pode haver dois sinais +/- em um flutuador [Ee]. Eu aprendi mais algumas regras com o trabalho de Aulu e testei contra strings ruins, como '' '-' '-E-1' '0-0'. Aqui estão minhas ferramentas regex / substring / expr que parecem estar se mantendo:

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"

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


Qual é a versão mínima do bash? Eu apenas recebo bash: operador binário condicional esperado erro de sintaxe bash: perto de um token inesperado `= ~ '#
9119 Paul Hargreaves

1
O @PaulHargreaves =~existia pelo menos desde o bash 3.0.
Gilles 'SO- stop being evil'

@PaulHargreaves você provavelmente teve um problema com seu primeiro operando, por exemplo, muitas aspas ou similar #
Joshua Clayton

@JoshuaClayton perguntei sobre a versão porque é muito muito velho bash em uma caixa de Solaris 7, o que ainda temos e ele não suporta = ~
Paul Hargreaves

6

Isso pode ser alcançado usando-se greppara verificar se a variável em questão corresponde a uma expressão regular estendida.

Inteiro de teste 1120:

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

Resultado: Valid number.

Teste não inteiro 1120a:

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

Resultado: Error: not a number.


Explicação

  • O grep, a -Echave que nos permite usar expressão regular estendida '^[0-9]+$'. Essa expressão regular significa que a variável deve []conter apenas os números de 0-9zero a nove do ^início ao $final da variável e deve ter pelo menos +um caractere.
  • A grep, o -qinterruptor tranquila desliga qualquer saída ou não encontra nada.
  • if verifica o status de saída de grep . Status de saída 0significa sucesso e qualquer coisa maior significa erro. O grepcomando tem um status de saída 0se encontrar uma correspondência e 1quando não encontrar;

Então, reunindo tudo, no ifteste, nós echoa variável $yournumbere a |canalizamos para a grepqual, com a -qopção, corresponde silenciosamente à -Eexpressão de '^[0-9]+$'expressão regular estendida . O status de saída de grepserá0 se grepencontrou com sucesso um jogo e 1se ele não o fez. Se conseguiu combinar, nós echo "Valid number.". Se ele não corresponder, nós echo "Error: not a number.".


Para carros alegóricos ou duplos

Podemos apenas mudar a expressão regular de '^[0-9]+$'para '^[0-9]*\.?[0-9]+$'para carros alegóricos ou duplos.

Flutuador de teste 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

Resultado: Valid number.

Flutuador de teste 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

Resultado: Error: not a number.


Para negativos

Para permitir números inteiros negativos, basta alterar a expressão regular de '^[0-9]+$'para '^\-?[0-9]+$'.

Para permitir flutuações ou dobras negativas, basta alterar a expressão regular de '^[0-9]*\.?[0-9]+$'para '^\-?[0-9]*\.?[0-9]+$'.


Você está certo, @CharlesDuffy. Obrigado. Eu limpei a resposta.
Joseph Shih

Certo, você está de novo, @CharlesDuffy. Fixo!
Joseph Shih

1
LGTM; a resposta conforme editada tem o meu +1. As únicas coisas que eu faria de diferente neste momento são apenas questões de opinião e não de correção (f / e, usar em [-]vez de \-e em [.]vez de \.é um pouco mais detalhado, mas isso significa que suas seqüências de caracteres não precisam mudar, se são usados ​​em um contexto em que as barras invertidas são consumidas).
Charles Duffy

4

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

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

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

Numéricos incluirão espaço, o ponto decimal e "e" ou "E" para ponto flutuante.

Porém, se você especificar um número hexadecimal no 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-o como um dígito (não é isso de alguma peculiaridade estranha dos compiladores do GCC que permitem usar a notação 0x para bases diferentes de 16 ??? )

Convém testar "0x" ou "0X" antes de testar se é numérico se sua entrada não for totalmente 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

17
[[ $VAR = *[[:digit:]]* ]]retornará true se a variável contiver um número, não se for um número inteiro.
Glenn Jackman

[[ "z3*&" = *[[:digit:]]* ]] && echo "numeric"impressões numeric. Testado na versão bash 3.2.25(1)-release.
Jdamian 8/09/16

1
@ultraswadable, sua solução detecta aquelas seqüências que contêm, pelo menos, um dígito cercado (ou não) por outros caracteres. Eu voto negativo.
Jdamian 8/09/16

A abordagem obviamente correta é, portanto, para reverter isso, e uso[[ -n $VAR && $VAR != *[^[:digit:]]* ]]
eschwartz

@eschwartz, sua solução não funciona com números negativos
Anjo

4

A maneira mais simples é verificar se ele contém caracteres que não sejam dígitos. Você substitui todos os caracteres de dígito por nada e verifica o comprimento. Se houver comprimento, não é um número.

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

2
Lidar com números negativos exigiria uma abordagem mais complicada.
187 Andy

... Ou um sinal positivo opcional.
tripleee

Tripleee @ Eu gostaria de ver a sua abordagem, se você souber como fazê-lo.
Andy Andy

4

Como eu tive que mexer com isso ultimamente e como o apelo de karttu com o teste de unidade, mais. Revisei o código e adicionei outras soluções também, tente você mesmo 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 números inteiros e flutuantes, onde por outro lado isFloat () retorna FALSE em valores inteiros e isInteger () da mesma forma retorna FALSE em flutuadores. Para sua conveniência, tudo em um só 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; }

Pessoalmente, eu removeria a functionpalavra-chave, pois ela não faz nada de útil. Além disso, não tenho certeza sobre a utilidade dos valores de retorno. Salvo indicação em contrário, as funções retornarão o status de saída do último comando, para que você não precise fazer returnnada.
Tom Fenech

Bom, de fato, os returns são confusos e o tornam menos legível. Usar functionpalavras-chave ou não é mais uma questão de sabor pessoal, pelo menos eu as removi dos forros para economizar espaço. valeu.
3ronco 09/09/16

Não se esqueça que pontos e vírgulas são necessários após os testes para as versões de uma linha.
Tom Fenech

2
isNumber retornará 'true' em qualquer string que tenha um número.
DrStrangepork

@DrStrangepork De fato, minha matriz false_values ​​está faltando nesse caso. Eu vou dar uma olhada nisso. Obrigado pela dica.
3ronco 24/10

4

Eu uso expr . Retorna um 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 o bc se você não precisar de números inteiros, mas não acredito que bctenha o mesmo comportamento. Se você adicionar zero a um número não, você receberá zero e também retornará um valor zero. Talvez você possa combinar bce expr. Use bcpara adicionar zero a $number. Se a resposta for 0, tente exprverificar $numberse não é zero.


1
Isso é muito ruim. Para torná-lo um pouco melhor, você deve usar expr -- "$number" + 0; no entanto, isso ainda vai fingir isso 0 isn't a number. De man expr:Exit status is 0 if EXPRESSION is neither null nor 0, 1 if EXPRESSION is null or 0,
gniourf_gniourf

3

Eu gosto da resposta de Alberto Zaccagni.

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

Pré-requisitos importantes: - nenhum sub-shell gerado - nenhum analisador de ER invocado - a maioria dos aplicativos de shell não usa números reais

Mas se $varé complexo (por exemplo, um acesso associativo à matriz) e se o número for um número inteiro não negativo (na maioria dos casos de uso), isso talvez seja mais eficiente?

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

2

Uso printf como as outras respostas mencionadas, se você fornecer a string de formato "% f" ou "% i" printf fará a verificação por você. Mais fácil do que reinventar as verificações, a sintaxe é simples e curta e a impressão é onipresente. Portanto, é uma escolha decente na minha opinião - você também pode usar a seguinte idéia para verificar uma variedade de coisas, não apenas útil para verificar números.

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

expressão regular vs expansão de parâmetro

Como resposta de Charles Duffy funciona, mas use apenas expressão regular e sei que isso é lento, gostaria de mostrar outra maneira, usando apenasexpansão de parâmetro :

Apenas para números inteiros positivos:

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

Para números inteiros:

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

Então para flutuar:

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

Para corresponder à solicitação inicial:

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

Agora uma pequena comparação:

Existe uma mesma verificação com base na resposta de Charles Duffy :

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

Alguns testes:

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

OK, tudo bem. Agora, quanto tempo levará tudo isso (no meu raspberry pi):

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

Depois, com expressões regulares:

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

Concordo, de qualquer forma, prefiro não usar regex, quando eu poderia usar a expansão de parâmetros ... O abuso do RE tornará o script do bash mais lento
F. Hauri

Resposta editada: Não há necessidade de extglob(e um pouco mais rápido)!
F. Hauri

@CharlesDuffy, no meu raspberry pi, a iteração 1000 levará 2,5 segundos na minha versão e 4,4 segundos na sua versão!
F. Hauri

Não posso discutir com isso. Du
Charles Duffy

1

Para pegar números negativos:

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

Além disso, isso requer que o globbing estendido seja ativado primeiro. Este é um recurso somente Bash, que está desativado por padrão.
Tripleee

@tripleee estendida englobamento é ativado automaticamente quando usando == ou =! 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 Elmers

@BadrElmers Obrigado pela atualização. Este parece ser um novo comportamento que não é verdadeiro no meu Bash 3.2.57 (MacOS Mojave). Vejo que funciona como você descreve em 4.4.
Tripleee 13/05/19

1

Você pode usar "let" assim:

[ ~]$ 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
[ ~]$ 

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


5
Isso é muito perigoso. Não avalie a aritmética não validada no shell. Ele deve ser validado de outra maneira primeiro.
Ormaaj

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

Remova o -\?padrão de correspondência grep se você não aceitar um número inteiro negativo.


2
Voto por falta de explicação. Como é que isso funciona? Parece complexo e quebradiço, e não é óbvio quais entradas exatamente ele aceitará. (Por exemplo, é a remoção de espaços crucialmente necessário Por que Ele vai dizer um número com espaços incorporados é um número válido, o que pode não ser desejável?.)
tripleee

1

Fiz a mesma coisa aqui com uma expressão regular que testa a parte inteira e a parte decimal, separadas por um ponto.

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

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

Você poderia explicar por que sua resposta é fundamentalmente diferente de outras respostas antigas, por exemplo, a resposta de Charles Duffy? Bem, a resposta é realmente quebrado uma vez que valida um único período.
gniourf_gniourf

não tenho certeza de entender o período único aqui ... é de um ou zero período esperado ... Mas, nada bem fundamentalmente diferente, apenas achei o regex mais fácil de ler.
Jerome

Também usando * deve corresponder casos mundo mais reais
Jerome

A coisa é que você está encontrando a string vazia a=''ea string que contém um período única a='.'para que o seu código é um pouco quebrado ...
gniourf_gniourf

0

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

Quase correto (você está aceitando a corda vazia), mas gratamente complicado a ponto de ofuscar.
Gilles 'SO- stop be evil'

2
Incorreto: você está aceitando -netc. (por causa de echo) e está aceitando variáveis ​​com novas linhas à direita (por causa de $(...)). E, a propósito, printnão é um comando shell válido.
gniourf_gniourf

0

Tentei a receita da lâmina ultrasaw, pois parecia a mais prática para mim, e não consegui fazê-la funcionar. No final, eu criei outra maneira, com base em outros na substituição de parâmetros, desta vez com substituição de regex:

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

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

O que eu gosto sobre este é sua pegada pequena e flexibilidade. Nesse formulário, ele funciona apenas para números inteiros de base 10 não delimitados, embora certamente você possa usar a correspondência de padrões para adequá-lo a outras necessidades.


Lendo a solução de mrucci, parece quase o mesmo que o meu, mas usando a substituição regular de cordas em vez do "estilo sed". Ambos usam as mesmas regras para correspondência de padrões e são, AFAIK, soluções intercambiáveis.
ata

sedé POSIX, enquanto sua solução é bash. Ambos têm seus usos
v010dya 11/04

0

Rápido e sujo: Eu sei que não é o caminho mais elegante, mas geralmente adiciono zero a ele e testo 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 bombeará se $ 1 NÃO for um número 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
}

OBSERVAÇÃO: Acho que nunca pensei em procurar tipos flutuantes ou mistos que fizessem o script inteiro explodir ... no meu caso, eu não queria que isso fosse mais longe. Vou brincar com a solução de mrucci e o regex de Duffy - eles parecem os mais robustos dentro da estrutura do bash ...


4
Isso aceita expressões aritméticas como 1+1, mas rejeita alguns números inteiros positivos com 0s iniciais (porque 08é uma constante octal inválida).
Gilles 'SO- stop be evil'

2
Isso tem outros problemas também: 0não é um número, e é sujeito a injeção de código arbitrário, experimentá-lo: isInteger 'a[$(ls)]'. Opa.
gniourf_gniourf

1
E a expansão de $((...))é sem aspas, um numérico IFS=123irá alterá-lo.
Isaac
Ao utilizar nosso site, você reconhece que leu e compreendeu nossa Política de Cookies e nossa Política de Privacidade.
Licensed under cc by-sa 3.0 with attribution required.