Estou tentando fazer algo bastante comum: analisar a entrada do usuário em um script de shell. Se o usuário forneceu um número inteiro válido, o script faz uma coisa e, se não for válido, faz outra. O problema é que não encontrei uma maneira fácil (e razoavelmente elegante) de fazer isso - eu não quero ter que separá-lo char por char.
Sei que deve ser fácil, mas não sei como. Eu poderia fazer isso em uma dúzia de idiomas, mas não em BASH!
Em minha pesquisa, descobri o seguinte:
Expressão regular para testar se uma string consiste em um número real válido na base 10
E há uma resposta aqui que fala sobre regex, mas até onde eu sei, essa é uma função disponível em C (entre outras). Ainda assim, ele teve o que parecia ser uma ótima resposta, então tentei com grep, mas grep não sabia o que fazer com ele. Tentei -P, que na minha caixa significa tratá-lo como uma expressão regular PERL - nada. Traço E (-E) também não funcionou. E nem -F.
Só para ficar claro, estou tentando algo assim, procurando qualquer saída - a partir daí, vou hackear o script para aproveitar o que conseguir. (IOW, eu esperava que uma entrada não conforme retornasse nada enquanto uma linha válida fosse repetida.)
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
Alguém poderia ilustrar como isso é feito mais facilmente?
Francamente, isso é uma lacuna do TEST, na minha opinião. Deve ter uma bandeira como esta
if [ -I "string" ] ;
then
echo "String is a valid integer."
else
echo "String is not a valid integer."
fi
fonte
[
é compatível com o antigotest
;[[
é a novidade do Bash, com mais operações e diferentes regras de cotação. Se você já decidiu ficar com o Bash, vá em frente[[
(é realmente muito melhor); se precisar de portabilidade para outros shells, evite[[
completamente.Respostas:
^
indica o início do padrão de entrada-
é um "-" literal?
meios "0 ou 1 do anterior (-
)"+
meios "1 ou mais das formas anteriores ([0-9]
)"$
indica o fim do padrão de entradaPortanto, a regex corresponde a um opcional
-
(no caso de números negativos), seguido por um ou mais dígitos decimais.Referências :
fonte
+
significa "1 ou mais do precedente" e$
indica o fim do padrão de entrada. Portanto, a regex corresponde a um opcional-
seguido por um ou mais dígitos decimais.[A-z]
não só dar-lheA-Z
ea-z
, mas também\
,[
,]
,^
,_
, e`
.d[g-i]{2}
pode terminar não apenas em correspondência,dig
mas tambémdish
no agrupamento sugerido por essa resposta (onde osh
dígrafo é considerado um único caractere, agrupado depoish
).Uau ... existem tantas soluções boas aqui !! De todas as soluções acima, concordo com @nortally que usar
-eq
um liner é o mais legal.Estou executando o GNU bash, versão
4.1.5
(Debian). Eu também verifiquei isso no ksh (SunSO 5.10).Aqui está minha versão para verificar se
$1
é um número inteiro ou não:Esta abordagem também leva em conta os números negativos, que algumas das outras soluções terão um resultado negativo defeituoso e permitirá um prefixo de "+" (por exemplo, +30) que obviamente é um número inteiro.
Resultados:
A solução fornecida por Ignacio Vazquez-Abrams também foi muito legal (se você gosta de regex) depois que foi explicada. No entanto, ele não lida com números positivos com o
+
prefixo, mas pode ser facilmente corrigido como a seguir:fonte
Um retardatário para a festa aqui. Estou extremamente surpreso por nenhuma das respostas mencionar a solução mais simples, rápida e portátil; a
case
declaração.O corte de qualquer sinal antes da comparação parece um pouco um hack, mas isso torna a expressão para a declaração de caso muito mais simples.
fonte
''|*[!0-9]*)
Eu gosto da solução usando o
-eq
teste, porque é basicamente um one-liner.Minha própria solução foi usar a expansão dos parâmetros para jogar fora todos os numerais e ver se restava algo. (Ainda estou usando 3.0, não usei
[[
nemexpr
antes, mas fico feliz em conhecê-los.)fonte
[ -z "${INPUT_STRING//[0-9]}" ]
mas uma solução muito boa!-eq
solução tem alguns problemas; veja aqui: stackoverflow.com/a/808740/1858225Para portabilidade para pré-Bash 3.1 (quando o
=~
teste foi introduzido), useexpr
.expr STRING : REGEX
procura REGEX ancorado no início de STRING, ecoando o primeiro grupo (ou duração da correspondência, se nenhuma) e retornando sucesso / falha. Esta é uma sintaxe regex antiga, daí o excesso\
.-\?
significa "talvez-
",[0-9]\+
significa "um ou mais dígitos" e$
significa "fim da string".O Bash também oferece suporte a globs estendidos, embora não me lembre de qual versão em diante.
@(-|)
significa "-
ou nada",[0-9]
significa "dígito" e*([0-9])
significa "zero ou mais dígitos".fonte
awk
,~
foi o operador "correspondência de regex". Em Perl (como copiado de C),~
já era usado para "complemento de bits", então eles usaram=~
. Esta notação posterior foi copiada para vários outros idiomas. (Perl 5.10 e Perl 6 gostam~~
mais, mas isso não tem impacto aqui.) Suponho que você possa olhar para isso como uma espécie de igualdade aproximada ...Aqui está mais uma abordagem sobre ele (usando apenas o comando test builtin e seu código de retorno):
fonte
$()
comif
. Isso funciona:if is_int "$input"
. Além disso, o$[]
formulário está obsoleto. Use em seu$(())
lugar. Dentro de qualquer um deles, o cifrão pode ser omitido:echo "Integer: $((input))"
colchetes não são necessários em nenhum lugar do seu script.test
não parece apoiar isso.[[
faz, no entanto.[[ 16#aa -eq 16#aa ]] && echo integer
imprime "inteiro".[[
retorna falsos positivos para este método; por exemplo,[[ f -eq f ]]
é bem-sucedido. Portanto, deve usartest
ou[
.Você pode retirar os não dígitos e fazer uma comparação. Aqui está um script de demonstração:
Esta é a aparência da saída do teste:
fonte
${var//string}
e${var#string}
e na seção chamada "Correspondência de padrões" para [^ [: digit:]] `(que também é abordado emman 7 regex
).match=${match#0*}
que não tira zeros à esquerda, ele retira no máximo um zero. Usando a expansão, isso só pode ser feito usandoextglob
viamatch=${match##+(0)}
.09
não é um número inteiro se você considerar que um número inteiro não tem zeros à esquerda. O teste é se o input (09
) é igual a uma versão higienizada (9
- um inteiro) e não.Para mim, a solução mais simples era usar a variável dentro de uma
(())
expressão, assim:Obviamente, essa solução só é válida se um valor zero não fizer sentido para o seu aplicativo. Aconteceu que isso era verdade no meu caso, e isso é muito mais simples do que as outras soluções.
Conforme apontado nos comentários, isso pode torná-lo sujeito a um ataque de execução de código: O
(( ))
operador avaliaVAR
, conforme declarado naArithmetic Evaluation
seção de página do manual bash (1) . Portanto, você não deve usar esta técnica quando a fonte do conteúdoVAR
é incerta (nem deve usar QUALQUER outra forma de expansão de variável, é claro).fonte
if (( var )); then echo "$var is an int."; fi
VAR='a[$(ls)]'; if ((VAR > 0)); then echo "$VAR is a positive integer"; fi
. Neste ponto, você está feliz por eu não ter inserido algum comando maligno em vez dels
. Como o OP menciona a entrada do usuário , eu realmente espero que você não esteja usando isso com a entrada do usuário no código de produção!agent007
ou com sed:
fonte
test -z "${string//[0-9]/}" && echo "integer" || echo "no integer"
... embora isso basicamente duplique a resposta de Dennis Williamsonif [[ -n "$(printf "%s" "${2}" | sed s/[0-9]//g)" ]]; then
Somando-se à resposta de Ignacio Vazquez-Abrams. Isso permitirá que o sinal + preceda o número inteiro e permitirá qualquer número de zeros como casas decimais. Por exemplo, isso permitirá que +45,00000000 seja considerado um número inteiro.
No entanto, $ 1 deve ser formatado para conter um ponto decimal. 45 não é considerado um número inteiro aqui, mas 45,0 é.
fonte
^[-+]?[0-9]
...?Para rir, eu desenvolvi quase que rapidamente um conjunto de funções para fazer isso (is_string, is_int, is_float, é string alfa ou outro), mas existem maneiras mais eficientes (menos código) de fazer isso:
Executei alguns testes aqui, eu defini que -44 é um int, mas 44- não é etc.:
Resultado:
NOTA: Os zeros iniciais podem inferir outra coisa ao adicionar números, como octal, então seria melhor removê-los se você pretende tratar '09' como um int (o que estou fazendo) (por exemplo,
expr 09 + 0
ou tira com sed)fonte