745 votes

Comment puis-je tester si une variable est un nombre en bash ?

Je n'arrive pas à comprendre comment je peux m'assurer qu'un argument passé à mon script est un nombre ou non.

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

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

Une aide ?

MISE À JOUR : J'ai réussi (avec L'aide de Charles ) pour le faire, mais je ne suis pas encore sûr que ce soit la meilleure façon de le faire (même si cela a fonctionné lors de mes tests). Voici comment cela s'est terminé :

[[ $1 =~ "^[0-9]+$" ]] && echo "number" && exit 0 || echo "not a number" && exit 1

20 votes

Soit dit en passant, le test && echo "foo" && exit 0 || echo "bar" && exit 1 que vous utilisez peut avoir des effets secondaires inattendus -- si l'écho échoue (peut-être que la sortie est vers un FD fermé), la fonction exit 0 sera ignoré, et le code essaiera alors de echo "bar" . S'il échoue également à ce niveau, le && échouera, et il n'exécutera même pas exit 1 ! En utilisant les if plutôt que de && / || est moins sujet à des effets secondaires inattendus.

0 votes

@CharlesDuffy C'est le genre de réflexion vraiment intelligente que la plupart des gens n'ont que lorsqu'ils doivent traquer des bugs poilus... ! Je n'avais jamais pensé que l'écho pouvait renvoyer un échec.

11 votes

J'arrive un peu tard dans la soirée, mais je connais les dangers dont Charles a parlé, car j'y ai été confronté il y a quelque temps. 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 sans aucun doute le cas avec () parenthèses utilisées à la place). Avertissement : Ne manquez jamais le point-virgule final . Sinon, vous risquez de provoquer bash pour imprimer les messages d'erreur les plus laids (et les plus inutiles)...

1000voto

Charles Duffy Points 34134

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

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 nombre entier, envisagez de modifier la regex de manière appropriée ; par exemple :

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

...ou, pour gérer les nombres négatifs :

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

8 votes

+1 pour cette approche, mais faites attention aux décimales, faire ce test avec, par exemple, "1.0" ou "1,0" imprime "error : Pas un nombre".

1 votes

@lhunath - vrai 'nuff. J'ai tendance à l'utiliser dans des gestionnaires d'erreurs plus complexes (c'est-à-dire beaucoup plus qu'un seul "echo" suivant), mais l'habitude s'est perdue ici.

1 votes

^-*[0-9]+([.][0-9]+)?$ pour tester également les nombres négatifs

355voto

jilles Points 4241

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

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

Cela rejette les chaînes vides et les chaînes contenant des caractères non numériques, mais accepte 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 ajouter d'autres "mauvais" modèles contenant les utilisations inappropriées de ceux-ci ( ?*-* / *.*.* )

25 votes

+1 -- c'est une méthode idiomatique, portable depuis l'interpréteur de commandes Bourne original, et qui a un support intégré pour les caractères génériques de type glob. Si vous venez d'un autre langage de programmation, cela semble étrange, mais c'est beaucoup plus élégant que de se débrouiller avec la fragilité des diverses questions de citation et les interminables problèmes de compatibilité amont/aval de if test ...

6 votes

Vous pouvez changer la première ligne en ${string#-} (qui ne fonctionne pas dans les anciens shells Bourne, mais fonctionne dans tout shell POSIX) pour accepter les entiers négatifs.

4 votes

De plus, il est facile d'étendre ce système aux flottants -- il suffit d'ajouter '.' | *.*.* aux motifs non autorisés, et ajouter le point aux caractères autorisés. De même, vous pouvez autoriser un signe optionnel avant, bien qu'alors je préférerais case ${string#[-+]} d'ignorer simplement le panneau.

228voto

Alberto Zaccagni Points 11478

J'utilise ceci :

"$var" -eq "$var"

comme dans :

#!/bin/bash

var=a

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

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

EDIT : Cette approche ne se comporte pas correctement avec les nombres avec point décimal. C'est ma faute.

9 votes

Je recommande toujours cette méthode (mais en mettant les variables entre guillemets pour tenir compte des chaînes vides), puisque le résultat est garanti comme suit utilisable comme un nombre dans Bash, quoi qu'il arrive.

21 votes

Veillez à utiliser des parenthèses simples ; [[ a -eq a ]] évalue à true (les deux arguments sont convertis en zéro)

0 votes

C'est en effet très soigné et fonctionne en bash (ce qui a été demandé) et la plupart des autres shells, mais malheureusement pas en ksh . Si vous voulez être portable, utilisez la solution de Jilles.

50voto

mrucci Points 1917

Ceci teste si un nombre est un entier non négatif et est à la fois indépendant de l'interpréteur de commandes (c'est-à-dire sans bashismes) et n'utilise que des modules intégrés à l'interpréteur de commandes :

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

MAIS C'EST FAUX .
Comme jilles commenté et suggéré dans sa réponse c'est la façon correcte de le faire en utilisant les modèles de shell.

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

6 votes

Cela ne fonctionne pas correctement, il accepte n'importe quelle chaîne commençant par un chiffre. Notez que WORD dans ${VAR##WORD} et similaire est un motif shell, pas une expression régulière.

3 votes

Pouvez-vous traduire cette expression en anglais, s'il vous plaît ? J'ai vraiment envie de l'utiliser, mais je ne la comprends pas assez pour lui faire confiance, même après avoir parcouru la page man de bash.

4 votes

*[!0-9]* est un motif qui correspond à toutes les chaînes de caractères contenant au moins un caractère non numérique. ${num##*[!0-9]*} est une "expansion de paramètres" où nous prenons le contenu de l'élément num et supprime la chaîne la plus longue qui correspond au motif. Si le résultat de l'expansion du paramètre n'est pas vide ( ! [ -z ${...} ] ), il s'agit d'un nombre puisqu'il ne contient aucun caractère non numérique.

27voto

pixelbeat Points 12073

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

isnumber() { test "$1" && printf '%f' "$1" >/dev/null 2>&1; }

Remplacez '%f' par le format particulier que vous souhaitez.

4 votes

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

0 votes

Belle solution. printf fonctionne vraiment bien, même les erreurs avec quelque chose d'approprié.

4 votes

@sputnick votre version brise la sémantique inhérente (et utile) de la valeur de retour de la fonction originale. Donc, au lieu de cela, laissez simplement la fonction telle quelle, et utilisez-la : isnumber 23 && echo "this is a number" || echo "not a number"

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X