J'essaie de faire quelque chose d'assez commun: analyser les entrées utilisateur dans un script Shell. Si l'utilisateur a fourni un entier valide, le script fait une chose et s'il n'est pas valide, il fait autre chose. Le problème, c'est que je n'ai pas trouvé de moyen facile (et raisonnablement élégant) de le faire - je ne veux pas être obligé de le séparer caractère par caractère.
Je sais que cela doit être facile mais je ne sais pas comment. Je pouvais le faire dans une douzaine de langues, mais pas BASH!
Dans mes recherches, j'ai trouvé ceci:
Expression régulière pour tester si une chaîne est constituée d'un nombre réel valide en base 10
Et il y a une réponse à cela qui parle de regex, mais pour autant que je sache, c'est une fonction disponible en C (parmi d'autres). Néanmoins, il avait ce qui semblait être une excellente réponse, alors je l’ai essayé avec grep, mais ce dernier ne savait pas quoi en faire. J'ai essayé -P, ce qui sur ma boîte signifie le traiter comme une expression rationnelle Perl. Dash E (-E) n'a pas fonctionné non plus. Et non plus -F.
Soyons clairs. J'essaie quelque chose comme ça, je cherche n'importe quelle sortie - à partir de là, je vais pirater le script pour tirer parti de tout ce que je reçois. (IOW, je m'attendais à ce qu'une entrée non conforme ne retourne rien alors qu'une ligne valide est répétée.)
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
Quelqu'un pourrait-il s'il vous plaît illustrer comment cela se fait le plus facilement?
Franchement, il s’agit d’un échec de TEST, à mon avis. Il devrait avoir un drapeau comme celui-ci
if [ -I "string" ] ;
then
echo "String is a valid integer."
else
echo "String is not a valid integer."
fi
[[ $var =~ ^-?[0-9]+$ ]]
^
indique le début du motif d'entrée-
est un littéral "-"?
signifie "0 ou 1 du précédent (-
)"+
signifie "un ou plusieurs des précédents ([0-9]
)"$
indique la fin du modèle d'entréeAinsi, l'expression régulière correspond à un -
optionnel (pour le cas de nombres négatifs), suivi d'un ou de plusieurs chiffres décimaux.
Références:
Wow ... il y a tellement de bonnes solutions ici !! De l’ensemble des solutions ci-dessus, je suis d’accord avec @nortally pour dire que l’utilisation du -eq
one liner est la solution la plus cool.
J'exécute GNU bash, version 4.1.5
(Debian). J'ai aussi vérifié cela sur ksh (SunSO 5.10).
Voici ma version de vérification si $1
est un entier ou non:
if [ "$1" -eq "$1" ] 2>/dev/null
then
echo "$1 is an integer !!"
else
echo "ERROR: first parameter must be an integer."
echo $USAGE
exit 1
fi
Cette approche prend également en compte les nombres négatifs, dont certaines solutions auront un résultat négatif erroné, et autorisera un préfixe "+" (par exemple +30) qui est évidemment un entier.
Résultats:
$ int_check.sh 123
123 is an integer !!
$ int_check.sh 123+
ERROR: first parameter must be an integer.
$ int_check.sh -123
-123 is an integer !!
$ int_check.sh +30
+30 is an integer !!
$ int_check.sh -123c
ERROR: first parameter must be an integer.
$ int_check.sh 123c
ERROR: first parameter must be an integer.
$ int_check.sh c123
ERROR: first parameter must be an integer.
La solution fournie par Ignacio Vazquez-Abrams était également très chouette (si vous aimez regex) après avoir été expliquée. Cependant, il ne gère pas les nombres positifs avec le préfixe +
, mais il peut facilement être corrigé comme suit:
[[ $var =~ ^[-+]?[0-9]+$ ]]
Plus tard à la fête ici. Je suis extrêmement surpris qu'aucune des réponses ne mentionne la solution la plus simple, la plus rapide et la plus portable; la déclaration case
.
case ${variable#[-+]} in
*[!0-9]* | '') echo Not a number ;;
* ) echo Valid number ;;
esac
L'élagage de n'importe quel signe avant la comparaison ressemble à un hack, mais cela simplifie considérablement l'expression de l'instruction case.
Pour la portabilité vers la version antérieure à Bash 3.1 (lorsque le test =~
a été introduit), utilisez expr
.
if expr "$string" : '-\?[0-9]\+$' >/dev/null
then
echo "String is a valid integer."
else
echo "String is not a valid integer."
fi
expr STRING : REGEX
recherche REGEX ancré au début de STRING, en faisant écho au premier groupe (ou à la longueur de la correspondance, si aucune) et en renvoyant succès/échec. Ceci est l'ancienne syntaxe regex, d'où l'excès \
. -\?
signifie "peut-être -
", [0-9]\+
signifie "un ou plusieurs chiffres" et $
signifie "fin de chaîne".
Bash prend également en charge les globs étendus, bien que je ne me rappelle plus à partir de quelle version.
shopt -s extglob
case "$string" of
@(-|)[0-9]*([0-9]))
echo "String is a valid integer." ;;
*)
echo "String is not a valid integer." ;;
esac
# equivalently, [[ $string = @(-|)[0-9]*([0-9])) ]]
@(-|)
signifie "-
ou rien", [0-9]
signifie "digit" et *([0-9])
signifie "zéro chiffre ou plus".
J'aime la solution utilisant le test -eq
, parce que c'est fondamentalement un one-liner.
Ma propre solution consistait à utiliser l’extension de paramètre pour éliminer tous les chiffres et voir s’il restait quelque chose. (J'utilise toujours la version 3.0, je n'ai jamais utilisé [[
ou expr
auparavant, mais je suis ravi de les rencontrer.)
if [ "${INPUT_STRING//[0-9]}" = "" ]; then
# yes, natural number
else
# no, has non-numeral chars
fi
Voici encore une autre version (en utilisant uniquement la commande intégrée test et son code de retour):
function is_int() { return $(test "$@" -eq "$@" > /dev/null 2>&1); }
input="-123"
if $(is_int "${input}");
then
echo "Input: ${input}"
echo "Integer: $[${input}]"
else
echo "Not an integer: ${input}"
fi
Pour moi, la solution la plus simple consistait à utiliser la variable dans une expression (())
, comme suit:
if ((VAR > 0))
then
echo "$VAR is a positive integer."
fi
Bien entendu, cette solution n’est valable que si une valeur de zéro n’a pas de sens pour votre application. Cela s’est avéré vrai dans mon cas, ce qui est beaucoup plus simple que les autres solutions.
Comme indiqué dans les commentaires, cela peut vous exposer à une attaque par exécution de code: L'opérateur (( ))
évalue VAR
, comme indiqué dans la section Arithmetic Evaluation
de la page de manuel bash (1) . Par conséquent, vous ne devez pas utiliser cette technique lorsque la source du contenu de VAR
est incertaine (vous ne devez pas non plus utiliser AUCUNE autre forme d’expansion de variable).
Vous pouvez effacer les non-chiffres et faire une comparaison. Voici un script de démonstration:
for num in "44" "-44" "44-" "4-4" "a4" "4a" ".4" "4.4" "-4.4" "09"
do
match=${num//[^[:digit:]]} # strip non-digits
match=${match#0*} # strip leading zeros
echo -en "$num\t$match\t"
case $num in
$match|-$match) echo "Integer";;
*) echo "Not integer";;
esac
done
Voici à quoi ressemble la sortie du test:
44 44 Entier - 44 44 Entier 44- 44 Pas entier 4-4 44 Pas entier A4 4 Pas entier 4a 4 Pas entier . 4 4 Pas entier 4.4 44 Pas entier - 4.4 44 Pas entier 09 9 Pas entier
Pour les éclats de rire, j'ai à peu près rapidement mis au point un ensemble de fonctions (is_string, is_int, is_float, is alpha string ou autre), mais il existe des moyens plus efficaces (moins de code) pour le faire:
#!/bin/bash
function strindex() {
x="${1%%$2*}"
if [[ "$x" = "$1" ]] ;then
true
else
if [ "${#x}" -gt 0 ] ;then
false
else
true
fi
fi
}
function is_int() {
if is_empty "${1}" ;then
false
return
fi
tmp=$(echo "${1}" | sed 's/[^0-9]*//g')
if [[ $tmp == "${1}" ]] || [[ "-${tmp}" == "${1}" ]] ; then
#echo "INT (${1}) tmp=$tmp"
true
else
#echo "NOT INT (${1}) tmp=$tmp"
false
fi
}
function is_float() {
if is_empty "${1}" ;then
false
return
fi
if ! strindex "${1}" "-" ; then
false
return
fi
tmp=$(echo "${1}" | sed 's/[^a-z. ]*//g')
if [[ $tmp =~ "." ]] ; then
#echo "FLOAT (${1}) tmp=$tmp"
true
else
#echo "NOT FLOAT (${1}) tmp=$tmp"
false
fi
}
function is_strict_string() {
if is_empty "${1}" ;then
false
return
fi
if [[ "${1}" =~ ^[A-Za-z]+$ ]]; then
#echo "STRICT STRING (${1})"
true
else
#echo "NOT STRICT STRING (${1})"
false
fi
}
function is_string() {
if is_empty "${1}" || is_int "${1}" || is_float "${1}" || is_strict_string "${1}" ;then
false
return
fi
if [ ! -z "${1}" ] ;then
true
return
fi
false
}
function is_empty() {
if [ -z "${1// }" ] ;then
true
else
false
fi
}
Effectué à travers quelques tests ici, j'ai défini que -44 est un int mais 44- n'est pas etc ..:
for num in "44" "-44" "44-" "4-4" "a4" "4a" ".4" "4.4" "-4.4" "09" "hello" "h3llo!" "!!" " " "" ; do
if is_int "$num" ;then
echo "INT = $num"
Elif is_float "$num" ;then
echo "FLOAT = $num"
Elif is_string "$num" ; then
echo "STRING = $num"
Elif is_strict_string "$num" ; then
echo "STRICT STRING = $num"
else
echo "OTHER = $num"
fi
done
Sortie:
INT = 44
INT = -44
STRING = 44-
STRING = 4-4
STRING = a4
STRING = 4a
FLOAT = .4
FLOAT = 4.4
FLOAT = -4.4
INT = 09
STRICT STRING = hello
STRING = h3llo!
STRING = !!
OTHER =
OTHER =
NOTE: Les 0 premiers peuvent en déduire autre chose lors de l'ajout de nombres tels que l'octal, il serait donc préférable de les supprimer si vous avez l'intention de traiter '09' comme un entier (ce que je fais) (par exemple, expr 09 + 0
ou supprimer avec sed)
Ajout à la réponse d'Ignacio Vazquez-Abrams. Cela permettra au signe + de précéder le nombre entier et permettra un nombre quelconque de zéros sous forme de points décimaux. Par exemple, cela permettra à +45.00000000 d'être considéré comme un entier.
Cependant, $ 1 doit être formaté pour contenir un point décimal. 45 n'est pas considéré ici comme un entier, mais 45.0 l'est.
if [[ $1 =~ ^-?[0-9]+.?[0]+$ ]]; then
echo "yes, this is an integer"
Elif [[ $1 =~ ^\+?[0-9]+.?[0]+$ ]]; then
echo "yes, this is an integer"
else
echo "no, this is not an integer"
fi
ou avec sed:
test -z $(echo "2000" | sed s/[0-9]//g) && echo "integer" || echo "no integer"
# integer
test -z $(echo "ab12" | sed s/[0-9]//g) && echo "integer" || echo "no integer"
# no integer