Comment tester si une variable est un nombre dans Bash?

599

Je n'arrive pas à comprendre comment puis-je m'assurer qu'un argument transmis à mon script est un nombre ou non.

Tout ce que je veux faire, c'est quelque chose comme ceci:

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

De l'aide?

Flávio Amieiro
la source
17
En passant - l' test && echo "foo" && exit 0 || echo "bar" && exit 1approche que vous utilisez peut avoir des effets secondaires imprévus - si l'écho échoue (la sortie est peut-être vers un FD fermé), le exit 0sera ignoré et le code essaiera ensuite de le faire echo "bar". Si cela échoue également, la &&condition échouera et ne s'exécutera même pas exit 1! L'utilisation de ifdéclarations réelles plutôt que &&/ ||est moins sujette à des effets secondaires inattendus.
Charles Duffy
@CharlesDuffy C'est le genre de pensée vraiment intelligente à laquelle la plupart des gens ne parviennent que lorsqu'ils doivent traquer des insectes velus ...! Je n'ai jamais pensé que l'écho pouvait retourner l'échec.
Camilo Martin
6
Un peu tard pour la fête, mais je connais les dangers dont Charles a parlé, car j'ai dû les traverser il y a un certain temps aussi. Voici donc une ligne 100% infaillible (et bien lisible) pour vous: [[ $1 =~ "^[0-9]+$" ]] && { echo "number"; exit 0; } || { echo "not a number"; exit 1; }les crochets indiquent que les choses ne doivent PAS être exécutées dans un sous-shell (ce qui serait certainement le cas avec des ()parenthèses utilisées à la place). Avertissement: ne manquez jamais le dernier point-virgule . Sinon, vous risquez bashd'imprimer les messages d'erreur les plus laids (et les plus inutiles) ...
syntaxerror
5
Cela ne fonctionne pas dans Ubuntu, sauf si vous ne supprimez pas les guillemets. Il devrait donc être juste[[ 12345 =~ ^[0-9]+$ ]] && echo OKKK || echo NOOO
Treviño
4
Vous devrez être plus précis sur ce que vous entendez par «nombre» . Un nombre entier? Un nombre à virgule fixe? Notation scientifique ("e")? Existe-t-il une plage requise (par exemple une valeur non signée 64 bits), ou autorisez-vous un nombre pouvant être écrit?
Toby Speight

Réponses:

803

Une approche consiste à utiliser une expression régulière, comme ceci:

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

Si la valeur n'est pas nécessairement un entier, envisagez de modifier la regex de manière appropriée; par exemple:

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

... ou, pour gérer les nombres avec un signe:

^[+-]?[0-9]+([.][0-9]+)?$
Charles Duffy
la source
7
+1 pour cette approche, mais attention aux décimales, en faisant ce test avec, par exemple, "1.0" ou "1,0" imprime l'erreur: Pas un nombre ".
sourcerebels
15
Je trouve le '' exec> & 2; écho ... '' plutôt idiot. Juste '' echo ...> & 2 ''
lhunath
5
@Ben voulez-vous vraiment gérer plus d'un signe moins? Je le ferais ^-?plutôt que ^-*si vous ne faites pas le travail pour gérer correctement plusieurs inversions.
Charles Duffy
4
@SandraSchlichting Rend toutes les futures sorties vers stderr. Pas vraiment un point ici, où il n'y a qu'un seul écho, mais c'est une habitude que j'ai tendance à prendre pour les cas où les messages d'erreur s'étendent sur plusieurs lignes.
Charles Duffy
29
Je ne sais pas pourquoi l'expression régulière doit être enregistrée dans une variable, mais si c'est pour des raisons de compatibilité, je ne pense pas que ce soit nécessaire. Vous pouvez simplement appliquer directement l'expression: [[ $yournumber =~ ^[0-9]+$ ]].
konsolebox
284

Sans bashismes (fonctionne même dans le système V sh),

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

Cela rejette les chaînes vides et les chaînes contenant des non-chiffres, acceptant tout le reste.

Les nombres négatifs ou à virgule flottante nécessitent un travail supplémentaire. Une idée est d'exclure -/ .dans le premier "mauvais" modèle et d'ajouter plus de "mauvais" modèles contenant leurs utilisations inappropriées ( ?*-*/ *.*.*)

jilles
la source
20
+1 - c'est un moyen idiomatique et portable de revenir au shell Bourne d'origine, et a un support intégré pour les caractères génériques de style glob. Si vous venez d'un autre langage de programmation, cela a l'air étrange, mais c'est beaucoup plus élégant que de faire face à la fragilité de divers problèmes de citation et à des problèmes de compatibilité sans fin en arrière / latéral avecif test ...
tripleee
6
Vous pouvez changer la première ligne en ${string#-}(qui ne fonctionne pas dans les anciens shell Bourne, mais fonctionne dans n'importe quel shell POSIX) pour accepter les entiers négatifs.
Gilles 'SO- arrête d'être méchant'
4
En outre, cela est facile à étendre aux flottants - il suffit d'ajouter '.' | *.*.*aux motifs interdits et d'ajouter des points aux caractères autorisés. De même, vous pouvez autoriser un signe facultatif avant, bien que je préfère case ${string#[-+]}ignorer simplement le signe.
tripleee
Voir ceci pour gérer les entiers signés: stackoverflow.com/a/18620446/2013911
Niklas Peter
2
@Dor Les guillemets ne sont pas nécessaires, car la commande case n'effectue pas de fractionnement de mot ni de génération de chemin sur ce mot. (Cependant, l' expansion dans les modèles de cas peuvent avoir besoin de citer car il détermine si les caractères correspondants de modèle sont littérales ou spéciaux.)
Jilles
193

La solution suivante peut également être utilisée dans des shells basiques tels que Bourne sans avoir besoin d'expressions régulières. Fondamentalement, toute opération d'évaluation de valeur numérique utilisant des non-nombres entraînera une erreur qui sera implicitement considérée comme fausse dans le shell:

"$var" -eq "$var"

un péché:

#!/bin/bash

var=a

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

Vous pouvez également tester pour $? le code retour de l'opération qui est plus explicite:

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

La redirection de l'erreur standard est là pour masquer le message "expression entière attendue" que bash affiche au cas où nous n'aurions pas de nombre.

CAVEATS (merci aux commentaires ci-dessous):

  • Les nombres avec des décimales ne sont pas identifiés comme des "nombres" valides
  • Utiliser [[ ]]au lieu de [ ]sera toujours évalué àtrue
  • La plupart des shells non-Bash évalueront toujours cette expression comme true
  • Le comportement dans Bash n'est pas documenté et peut donc changer sans avertissement
  • Si la valeur inclut des espaces après le nombre (par exemple "1 a"), une erreur se produit, comme bash: [[: 1 a: syntax error in expression (error token is "a")
  • Si la valeur est la même que var-name (par exemple i = "i"), produit une erreur, comme bash: [[: i: expression recursion level exceeded (error token is "i")
Alberto Zaccagni
la source
8
Je recommanderais toujours cela (mais avec les variables citées pour autoriser les chaînes vides), car le résultat est garanti utilisable sous forme de nombre dans Bash, quoi qu'il arrive.
l0b0
21
Prenez soin d'utiliser des supports simples; [[ a -eq a ]]évalue à vrai (les deux arguments sont convertis à zéro)
Tgr
3
Très agréable! Notez que cela ne fonctionne que pour un entier, pas pour un nombre. J'ai dû vérifier un seul argument qui doit être un entier, donc cela a bien fonctionné:if ! [ $# -eq 1 -o "$1" -eq "$1" ] 2>/dev/null; then
haridsv
6
Je déconseille fortement cette méthode en raison du nombre non négligeable de coques dont la fonction [intégrée évaluera les arguments comme arithmétiques. Cela est vrai à la fois dans ksh93 et ​​mksh. De plus, étant donné que ces deux tableaux prennent en charge, il est facile d'injecter du code. Utilisez plutôt une correspondance de modèle.
ormaaj
3
@AlbertoZaccagni, dans les versions actuelles de bash, ces valeurs sont interprétées avec des règles de contexte numérique uniquement pour [[ ]]mais pas pour [ ]. Cela dit, ce comportement n'est pas spécifié à la fois par la norme POSIX testet dans la propre documentation de bash; les futures versions de bash pourraient modifier le comportement pour qu'il corresponde à ksh sans rompre les promesses comportementales documentées.
Charles Duffy
43

Cela teste si un nombre est un entier non négatif et est à la fois indépendant du shell (c'est-à-dire sans bashismes) et n'utilise que des shell intégrés:

INCORRECT.

Comme cette première réponse (ci-dessous) permet des nombres entiers avec des caractères tant que les premiers ne sont pas premiers dans la variable.

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

CORRECT .

Comme jilles l'a commenté et suggéré dans sa réponse, c'est la bonne façon de le faire en utilisant des motifs de coquille.

[ ! -z "${num##*[!0-9]*}" ] && echo "is a number" || echo "is not a number";
mrucci
la source
5
Cela ne fonctionne pas correctement, il accepte toute chaîne commençant par un chiffre. Notez que WORD dans $ {VAR ## WORD} et similaire est un modèle de shell, pas une expression régulière.
jilles
2
Pouvez-vous traduire cette expression en anglais, s'il vous plaît? Je veux vraiment l'utiliser, mais je ne le comprends pas suffisamment pour lui faire confiance, même après avoir lu la page de manuel de bash.
CivFan
2
*[!0-9]*est un modèle qui correspond à toutes les chaînes avec au moins 1 caractère non numérique. ${num##*[!0-9]*}est une "expansion de paramètre" où nous prenons le contenu de la numvariable et supprimons la chaîne la plus longue qui correspond au modèle. Si le résultat de l'expansion des paramètres n'est pas vide ( ! [ -z ${...} ]), alors c'est un nombre car il ne contient aucun caractère non numérique.
mrucci
Malheureusement, cela échoue s'il y a des chiffres dans l'argument, même s'il ne s'agit pas d'un nombre valide. Par exemple "exam1ple" ou "a2b".
studgeek
Les deux échouent avec122s :-(
Hastur
40

Personne n'a suggéré la correspondance étendue des motifs de bash :

[[ $1 == ?(-)+([0-9]) ]] && echo "$1 is an integer"
glenn jackman
la source
5
Glenn, je supprime shopt -s extglobde votre message (que j'ai surévalué, c'est l'une de mes réponses préférées ici), car dans les constructions conditionnelles, vous pouvez lire: Lorsque les opérateurs ==et !=sont utilisés, la chaîne à droite de l'opérateur est considérée comme un modèle et correspond selon les règles décrites ci-dessous dans Pattern Matching , comme si l' extgloboption shell était activée. J'espère que ça ne vous dérange pas!
gniourf_gniourf
Dans de tels contextes, vous n'avez pas besoin de shopt extglob... c'est une bonne chose à savoir!
gniourf_gniourf
Fonctionne bien pour les entiers simples.
2
@Jdamian: vous avez raison, cela a été ajouté dans Bash 4.1 (qui est sorti fin 2009 ... Bash 3.2 est sorti en 2006 ... c'est maintenant un logiciel ancien, désolé pour ceux qui sont coincés dans le passé). En outre, vous pouvez affirmer que les extglobs ont été introduits dans la version 2.02 (publiée en 1998) et ne fonctionnent pas dans les versions <2.02… Maintenant, votre commentaire ici servira d'avertissement concernant les anciennes versions.
gniourf_gniourf
1
Les variables à l'intérieur [[...]]ne sont pas sujettes au fractionnement de mots ou à l'expansion globale.
glenn jackman
26

Je suis surpris des solutions analysant directement les formats de nombres dans le shell. shell n'est pas bien adapté à cela, étant un DSL pour contrôler les fichiers et les processus. Il existe de nombreux analyseurs de nombres un peu plus bas, par exemple:

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
}

Remplacez «% f» par le format particulier dont vous avez besoin.

pixelbeat
la source
4
isnumber(){ printf '%f' "$1" &>/dev/null && echo "this is a number" || echo "not a number"; }
Gilles Quenot
4
@sputnick votre version rompt la sémantique de valeur de retour inhérente (et utile) de la fonction d'origine. Donc, à la place, laissez simplement la fonction telle quelle et utilisez-la:isnumber 23 && echo "this is a number" || echo "not a number"
michael
4
Cela ne devrait-il pas en être de même 2>/dev/nullpour que isnumber "foo"cela ne pollue pas stderr?
gioele
4
Appeler des shells modernes comme bash "une DSL pour contrôler les fichiers et les processus", c'est ignorer qu'ils sont utilisés pour bien plus que cela - certaines distributions ont construit des gestionnaires de packages et des interfaces Web entiers (aussi moche que cela puisse être). Les fichiers batch correspondent à votre description, car même la définition d'une variable est difficile.
Camilo Martin
7
C'est drôle que vous essayiez d'être intelligent en copiant des idiomes d'autres langues. Malheureusement, cela ne fonctionne pas dans les coquilles. Les shells sont très spéciaux, et sans connaissances solides à leur sujet, vous risquez d'écrire du code cassé. Votre code est cassé: isnumber "'a"retournera vrai. Ceci est documenté dans la spécification POSIX où vous lirez: Si le premier caractère est un guillemet simple ou un guillemet double, la valeur doit être la valeur numérique dans le jeu de codes sous-jacent du caractère suivant le guillemet simple ou le guillemet double .
gniourf_gniourf
16

Je regardais les réponses et ... réalisé que personne ne pensait aux nombres FLOAT (avec point)!

L'utilisation de grep est également très bien.
-E signifie regexp étendu
-q signifie silencieux (ne fait pas écho)
-qE est la combinaison des deux.

Pour tester directement dans la ligne de commande:

$ 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

Utilisation dans un 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

Pour faire correspondre les entiers JUST, utilisez ceci:

# change check line to:
check=`echo "$1" | grep -E ^\-?[0-9]+$`
Sergio Abreu
la source
Les solutions utilisant awk par triple_r et tripleee fonctionnent avec des flotteurs.
Ken Jackson
Merci pour cela et très bon point! Parce que la question est de savoir comment vérifier s'il s'agit d'un nombre et pas seulement d'un entier.
Tanasis
Je te remercie aussi Tanasis! Aidons-nous toujours.
Sergio Abreu
12

Juste un suivi à @mary. Mais parce que je n'ai pas assez de représentants, je ne pouvais pas poster ceci en tant que commentaire. Bref, voici ce que j'ai utilisé:

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

La fonction retournera "1" si l'argument est un nombre, sinon retournera "0". Cela fonctionne aussi bien pour les nombres entiers que pour les flottants. L'utilisation est quelque chose comme:

n=-2.05e+07
res=`isnum "$n"`
if [ "$res" == "1" ]; then
     echo "$n is a number"
else
     echo "$n is not a number"
fi
triple_r
la source
4
L'impression d'un nombre est moins utile que la définition d'un code de sortie. 'BEGIN { exit(1-(a==a+0)) }'est un peu difficile à grok mais peut être utilisé dans une fonction qui retourne vrai ou faux tout comme [, grep -q, etc.
tripleee
9
test -z "${i//[0-9]}" && echo digits || echo no no no

${i//[0-9]}remplace tout chiffre de la valeur de $ipar une chaîne vide, voir man -P 'less +/parameter\/' bash. -zvérifie si la chaîne résultante a une longueur nulle.

si vous souhaitez également exclure le cas lorsqu'il $iest vide, vous pouvez utiliser l'une de ces constructions:

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
user2683246
la source
Bravo surtout pour la man -P 'less +/parameter\/' bashpartie. Apprendre quelque chose de nouveau chaque jour. :)
David
@sjas Vous pouvez facilement ajouter \-une expression régulière pour résoudre le problème. Utilisez [0-9\-\.\+]pour tenir compte des flotteurs et des numéros signés.
user2683246
@sjas ok, ma faute
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

Pour mon problème, je devais seulement m'assurer qu'un utilisateur n'entre pas accidentellement du texte, j'ai donc essayé de le garder simple et lisible

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

Selon la page de manuel, cela fait à peu près ce que je veux

Si la valeur de l'expression est différente de zéro, l'état de retour est 0

Pour éviter les messages d'erreur désagréables pour les chaînes qui "pourraient être des nombres", j'ignore la sortie d'erreur

$ (( 2s ))
bash: ((: 2s: value too great for base (error token is "2s")
Hachi
la source
Cela ressemble plus à isInteger. Mais génial merci!
Naheel
8

Vieille question, mais je voulais juste clouer ma solution. Celui-ci ne nécessite pas d'étranges obus ou ne repose pas sur quelque chose qui n'a pas existé depuis toujours.

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

Fondamentalement, il supprime simplement tous les chiffres de l'entrée, et si vous vous retrouvez avec une chaîne de longueur non nulle, ce n'était pas un nombre.

Sammitch
la source
Cela échoue pour un vide var.
gniourf_gniourf
Ou pour les variables avec des retours à la ligne de fin ou quelque chose comme $'0\n\n\n1\n\n\n2\n\n\n3\n'.
gniourf_gniourf
7

Je ne peux pas encore commenter donc j'ajouterai ma propre réponse, qui est une extension de la réponse de Glenn Jackman en utilisant la correspondance de motifs bash.

Mon besoin initial était d'identifier les nombres et de distinguer les entiers et les flottants. Les définitions de fonction déduites:

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

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

J'ai utilisé des tests unitaires (avec shUnit2) pour valider mes modèles qui fonctionnaient comme prévu:

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

}

Remarques: Le modèle isFloat peut être modifié pour être plus tolérant au point décimal ( @(.,)) et au symbole E ( @(Ee)). Mes tests unitaires testent uniquement les valeurs entières ou flottantes, mais pas toute entrée non valide.

karttu
la source
Je suis désolé, je n'ai pas compris comment la fonction d'édition doit être utilisée.
3ronco
6

J'essaierais ceci:

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

Remarque: cela reconnaît nan et inf comme nombre.

débordé
la source
2
soit un doublon de, ou peut-être mieux adapté comme un commentaire à la réponse de pixelbeat (l'utilisation %fest probablement meilleure de toute façon)
michael
3
Au lieu de vérifier le code d'état précédent, pourquoi ne pas simplement le mettre en iflui-même? C'est ce qui iffait ...if printf "%g" "$var" &> /dev/null; then ...
Camilo Martin
3
Cela a d'autres mises en garde. Il validera la chaîne vide et les chaînes comme 'a.
gniourf_gniourf
6

Une réponse claire a déjà été donnée par @charles Dufy et d'autres. Une solution bash pure utiliserait ce qui suit:

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

Bien que pour les nombres réels, il n'est pas obligatoire d'avoir un nombre avant le point de radix .

Pour fournir un support plus complet des nombres flottants et de la notation scientifique (de nombreux programmes en C / Fortran ou bien exporteront float de cette façon), un ajout utile à cette ligne serait le suivant:

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

Menant ainsi à un moyen de différencier les types de numéro, si vous recherchez un type spécifique:

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

Remarque: Nous pourrions énumérer les exigences syntaxiques pour la notation décimale et scientifique, l'une étant d'autoriser la virgule comme point radix, ainsi que ".". Nous affirmerions alors qu'il ne doit y avoir qu'un seul point radix. Il peut y avoir deux signes +/- dans un flotteur [Ee]. J'ai appris quelques règles supplémentaires du travail d'Aulu et testé contre de mauvaises chaînes telles que '' '-' '-E-1' '0-0'. Voici mes outils regex / substring / expr qui semblent tenir le coup:

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
Aulo
la source
6
[[ $1 =~ ^-?[0-9]+$ ]] && echo "number"

N'oubliez pas -d'inclure des nombres négatifs!

D_I
la source
Quelle est la version minimale de bash? Je reçois juste bash: opérateur binaire conditionnel attendu bash: erreur de syntaxe près du jeton inattendu `= ~ '
Paul Hargreaves
1
@PaulHargreaves =~existait au moins depuis bash 3.0.
Gilles 'SO- arrête d'être méchant'
@PaulHargreaves vous avez probablement eu un problème avec votre premier opérande, par exemple trop de guillemets ou similaire
Joshua Clayton
@JoshuaClayton J'ai posé des questions sur la version car elle est très très ancienne bash sur une boîte Solaris 7, que nous avons toujours et qu'elle ne prend pas en charge = ~
Paul Hargreaves
6

Ceci peut être réalisé en utilisant greppour voir si la variable en question correspond à une expression régulière étendue.

Entier de test 1120:

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

Production: Valid number.

Test non entier 1120a:

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

Production: Error: not a number.


Explication

  • Le grep, le -Ecommutateur nous permet d'utiliser l'expression régulière étendue '^[0-9]+$'. Cette expression régulière signifie que la variable ne doit []contenir que les chiffres de 0-9zéro à neuf du ^début à la $fin de la variable et doit avoir au moins +un caractère.
  • Le grep, le -qcommutateur silencieux désactive toute sortie, qu'il trouve ou non quelque chose.
  • ifvérifie l'état de sortie de grep. Le statut de sortie 0signifie le succès et toute valeur supérieure signifie une erreur. La grepcommande a un statut de sortie 0si elle trouve une correspondance et 1quand elle ne le fait pas;

Donc, en mettant tout cela ensemble, dans le iftest, nous avons echola variable $yournumberet la |dirige vers celle grepqui avec le -qcommutateur correspond silencieusement à l' -Eexpression d' '^[0-9]+$'expression régulière étendue . Le statut de sortie de grepsera 0s'il grepa trouvé une correspondance avec succès et 1s'il ne l'a pas fait. Si réussi à correspondre, nous echo "Valid number.". Si cela ne correspondait pas, nous echo "Error: not a number.".


Pour flotteurs ou doubles

Nous pouvons simplement changer l'expression régulière de '^[0-9]+$'en '^[0-9]*\.?[0-9]+$'pour flottants ou doubles.

Flotteur d'essai 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

Production: Valid number.

Flotteur d'essai 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

Production: Error: not a number.


Pour les négatifs

Pour autoriser des entiers négatifs, changez simplement l'expression régulière de '^[0-9]+$'à '^\-?[0-9]+$'.

Pour autoriser des flottants ou des doubles négatifs, changez simplement l'expression régulière de '^[0-9]*\.?[0-9]+$'à '^\-?[0-9]*\.?[0-9]+$'.

Joseph Shih
la source
Vous avez raison, @CharlesDuffy. Merci. J'ai nettoyé la réponse.
Joseph Shih
Vous avez raison, @CharlesDuffy. Fixé!
Joseph Shih
1
LGTM; réponse telle que modifiée a mon +1. Les seules choses que je ferais différemment à ce stade ne sont que des questions d'opinion plutôt que de correction (f / e, utiliser à la [-]place de \-et [.]au lieu de \.est un peu plus verbeux, mais cela signifie que vos chaînes n'ont pas à changer si elles ' sont utilisés dans un contexte où les barres obliques inverses sont consommées).
Charles Duffy
4

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

Vous pouvez également utiliser les classes de personnages de bash.

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

Les valeurs numériques incluront l'espace, le point décimal et "e" ou "E" pour le point flottant.

Mais, si vous spécifiez un nombre hexadécimal de style C, c'est-à-dire "0xffff" ou "0XFFFF", [[: digit:]] renvoie true. Un peu un piège ici, bash vous permet de faire quelque chose comme "0xAZ00" et de toujours le compter comme un chiffre (n'est-ce pas d'une bizarrerie bizarre de compilateurs GCC qui vous permettent d'utiliser la notation 0x pour des bases autres que 16 ??? )

Vous voudrez peut-être tester "0x" ou "0X" avant de tester s'il s'agit d'un numérique si votre entrée n'est pas entièrement fiable, sauf si vous souhaitez accepter des nombres hexadécimaux. Cela serait accompli par:

if [[ ${VARIABLE:1:2} = "0x" ]] || [[ ${VARIABLE:1:2} = "0X" ]]; then echo "$VAR is not numeric"; fi
ultrasawblade
la source
17
[[ $VAR = *[[:digit:]]* ]]retournera vrai si la variable contient un nombre, pas s'il s'agit d' un entier.
glenn jackman
[[ "z3*&" = *[[:digit:]]* ]] && echo "numeric"impressions numeric. Testé en version bash 3.2.25(1)-release.
Jdamian
1
@ultraswadable, votre solution détecte les chaînes contenant au moins un chiffre entouré (ou non) par tout autre caractère. J'ai rétrogradé.
Jdamian
L'approche évidemment correcte est donc d'inverser cela, et d'utiliser[[ -n $VAR && $VAR != *[^[:digit:]]* ]]
eschwartz
@eschwartz, votre solution ne fonctionne pas avec des nombres négatifs
Angel
4

Le moyen le plus simple consiste à vérifier s'il contient des caractères non numériques. Vous remplacez tous les caractères numériques par rien et vérifiez la longueur. S'il y a de la longueur, ce n'est pas un nombre.

if [[ ! -n ${input//[0-9]/} ]]; then
    echo "Input Is A Number"
fi
Andy
la source
2
Pour gérer les nombres négatifs, il faudrait une approche plus compliquée.
Andy
... Ou un signe positif facultatif.
tripleee
@tripleee j'aimerais voir votre approche si vous savez comment le faire.
Andy
4

Comme j'ai dû falsifier ces derniers temps et comme l' approche de karttu avec le test unitaire le plus. J'ai révisé le code et ajouté quelques autres solutions aussi, essayez vous-même pour voir les résultats:

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

Donc isNumber () inclut des tirets, des virgules et une notation exponentielle et retourne donc TRUE sur les entiers et les flottants alors que d'autre part isFloat () renvoie FALSE sur les valeurs entières et isInteger () retourne également FALSE sur les flottants. Pour votre commodité tout en un:

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; }
3ronco
la source
Personnellement, je supprimerais le functionmot - clé car il ne fait rien d'utile. De plus, je ne suis pas sûr de l'utilité des valeurs de retour. Sauf indication contraire, les fonctions renverront l'état de sortie de la dernière commande, vous n'avez donc pas besoin dereturn rien même.
Tom Fenech
Sympa, en effet les returns sont déroutants et le rendent moins lisible. En utilisantfunction mots-clés ou non est plus une question de saveur personnelle, au moins je les ai supprimés des doublures pour économiser de l'espace. THX.
3ronco
N'oubliez pas que les points-virgules sont nécessaires après les tests pour les versions à une ligne.
Tom Fenech
2
isNumber retournera 'true' sur toute chaîne contenant un nombre.
DrStrangepork
@DrStrangepork En effet, mon tableau false_values ​​manque ce cas. Je vais devoir y réfléchir. Merci pour l'astuce.
3ronco
4

J'utilise expr . Il renvoie un non-zéro si vous essayez d'ajouter un zéro à une valeur non numérique:

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

Il pourrait être possible d'utiliser bc si vous avez besoin de non-entiers, mais je ne pense pas qu'il bcait le même comportement. L'ajout de zéro à un non-nombre vous donne zéro et renvoie également une valeur de zéro. Vous pouvez peut-être combiner bcet expr. Utilisez bcpour ajouter zéro à $number. Si la réponse est 0, essayez exprde vérifier que ce $numbern'est pas zéro.

David W.
la source
1
C'est plutôt mauvais. Pour le rendre légèrement meilleur, vous devez utiliser expr -- "$number" + 0; mais cela fera toujours semblant 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

J'aime la réponse d'Alberto Zaccagni.

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

Conditions préalables importantes: - aucun sous-shell généré - aucun analyseur RE invoqué - la plupart des applications shell n'utilisent pas de vrais nombres

Mais si $varest complexe (par exemple un accès à un tableau associatif), et si le nombre sera un entier non négatif (la plupart des cas d'utilisation), alors c'est peut-être plus efficace?

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

J'utilise printf comme les autres réponses mentionnées, si vous fournissez la chaîne de format "% f" ou "% i" printf fera la vérification pour vous. Plus simple que de réinventer les contrôles, la syntaxe est simple et courte et printf est omniprésent. Donc, c'est un choix décent à mon avis - vous pouvez également utiliser l'idée suivante pour vérifier une gamme de choses, ce n'est pas seulement utile pour vérifier les chiffres.

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


la source
2

expression régulière vs expansion des paramètres

Comme la réponse de Charles Duffy fonctionne, mais utilisez uniquementexpression régulière , et je sais que cela est lent, je voudrais montrer une autre façon, en utilisant uniquementexpansion des paramètres :

Pour les entiers positifs uniquement:

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

Pour les entiers:

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

Puis pour flotter:

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

Pour correspondre à la demande initiale:

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

Maintenant une petite comparaison:

Il y a un même contrôle basé sur la réponse de Charles Duffy :

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

Quelques tests:

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, c'est bon. Maintenant, combien de temps prendra tout cela (Sur ma framboise pi):

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

Puis avec des expressions régulières:

time for i in {1..1000};do cdIs_num +3.14159265;done
real    0m4.363s
user    0m2.168s
sys     0m0.000s
F. Hauri
la source
Je suis d'accord, de toute façon, je préfère ne pas utiliser l'expression régulière, quand je pourrais utiliser l'expansion des paramètres ... L'abus de RE ralentira le script bash
F. Hauri
Réponse éditée: Pas besoin de extglob(et un peu plus vite)!
F.Hauri
@CharlesDuffy, Sur mon framboise pi, 1000 itérations prendront 2,5sec avec ma version et 4,4sec avec votre version!
F.Hauri
Je ne peux pas discuter avec ça. 👍
Charles Duffy
1

Pour attraper des nombres négatifs:

if [[ $1 == ?(-)+([0-9.]) ]]
    then
    echo number
else
    echo not a number
fi
user28490
la source
En outre, cela nécessite que la globalisation étendue soit activée en premier. Il s'agit d'une fonctionnalité uniquement Bash qui est désactivée par défaut.
tripleee
@tripleee étendu globbing est activé automatiquement lorsque vous utilisez == 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 Merci pour la mise à jour. Cela semble être un nouveau comportement qui n'est pas vrai dans mon Bash 3.2.57 (MacOS Mojave). Je vois que cela fonctionne comme vous le décrivez en 4.4.
tripleee
1

Vous pouvez également utiliser "let" comme ceci:

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

Mais je préfère utiliser l'opérateur "= ~" Bash 3+ comme certaines réponses dans ce fil.

Idriss Neumann
la source
5
C'est très dangereux. N'évaluez pas l'arithmétique non validée dans le shell. Il doit d'abord être validé d'une autre manière.
ormaaj
1
printf '%b' "-123\nABC" | tr '[:space:]' '_' | grep -q '^-\?[[:digit:]]\+$' && echo "Integer." || echo "NOT integer."

Supprimez le -\?modèle de correspondance in grep si vous n'acceptez pas un entier négatif.

Ane Dijitak
la source
2
Downvote pour manque d'explication. Comment cela marche-t-il? Il a l'air complexe et fragile, et il n'est pas évident quelles entrées exactement il acceptera. (Par exemple, la suppression d'espaces est-elle absolument nécessaire? Pourquoi? Il dira qu'un nombre avec des espaces intégrés est un nombre valide, ce qui peut ne pas être souhaitable.)
tripleee
1

A fait la même chose ici avec une expression régulière qui teste la partie entière et la partie décimale, séparée par un point.

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

if [[ $1 =~ $re ]] 
then
   echo "is numeric"
else
  echo "Naahh, not numeric"
fi
Jérôme
la source
Pourriez-vous expliquer pourquoi votre réponse est fondamentalement différente des autres anciennes réponses, par exemple, la réponse de Charles Duffy? Eh bien, votre réponse est en fait cassée car elle valide une seule période.
gniourf_gniourf
pas sûr de comprendre la seule période ici ... c'est une ou zéro période attendue ... Mais rien de fondamentalement différent, juste trouvé l'expression régulière plus facile à lire.
Jerome
également utiliser * devrait correspondre à plus de cas du monde réel
Jerome
Le fait est que vous faites correspondre la chaîne vide a=''et la chaîne qui contient un point seulement, a='.'donc votre code est un peu cassé ...
gniourf_gniourf
0

J'utilise ce qui suit (pour les entiers):

## ##### 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
Marnix
la source
Presque correct (vous acceptez la chaîne vide) mais compliqué au point d'être obscurci.
Gilles 'SO- arrête d'être méchant'
2
Incorrect: vous acceptez -n, etc. (à cause de echo), et vous acceptez des variables avec des retours à la ligne de fin (à cause de $(...)). Et d'ailleurs, printn'est pas une commande shell valide.
gniourf_gniourf
0

J'ai essayé la recette d'ultrasawblade car elle me semblait la plus pratique et je n'ai pas pu la faire fonctionner. En fin de compte, j'ai imaginé une autre manière, basée sur d'autres comme la substitution de paramètres, cette fois avec le remplacement de l'expression régulière:

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

Il supprime chaque caractère: digit: class dans $ var et vérifie s'il nous reste une chaîne vide, ce qui signifie que l'original n'était que des nombres.

Ce que j'aime dans celui-ci, c'est son faible encombrement et sa flexibilité. Sous cette forme, cela ne fonctionne que pour les entiers de base 10 non délimités, bien que vous puissiez sûrement utiliser la correspondance de modèles pour l'adapter à d'autres besoins.

à
la source
En lisant la solution de mrucci, elle ressemble presque à la mienne, mais en utilisant un remplacement de chaîne régulier au lieu du "style sed". Les deux utilisent les mêmes règles pour la correspondance de motifs et sont, AFAIK, des solutions interchangeables.
ata
sedest POSIX, tandis que votre solution l'est bash. Les deux ont leurs utilisations
v010dya
0

Quick & Dirty: Je sais que ce n'est pas la manière la plus élégante, mais je viens généralement d'ajouter un zéro et de tester le résultat. ainsi:

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)) retournera 0 ou bombera si $ 1 n'est PAS un entier. par exemple:

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
}

NOTE: Je suppose que je n'ai jamais pensé à vérifier les flotteurs ou les types mixtes qui rendraient la bombe de script entière ... dans mon cas, je ne voulais pas que cela aille plus loin. Je vais jouer avec la solution de mrucci et le regex de Duffy - ils semblent les plus robustes dans le cadre bash ...

WWWIZARDS
la source
4
Cela accepte des expressions arithmétiques comme 1+1, mais rejette certains entiers positifs avec 0s en tête (car il 08s'agit d'une constante octale invalide).
Gilles 'SO- arrête d'être méchant'
2
Cela a aussi d' autres questions: 0est pas un nombre, et il est soumis à l' injection de code arbitraire, essayez: isInteger 'a[$(ls)]'. Oups.
gniourf_gniourf
1
Et l'expansion de $((...))n'est pas citée, un numérique la IFS=123changera.
Isaac