web-dev-qa-db-fra.com

Teste si la chaîne est un entier valide

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
98
Richard T
[[ $var =~ ^-?[0-9]+$ ]]
  • Le ^ indique le début du motif d'entrée
  • Le - est un littéral "-"
  • Le ? signifie "0 ou 1 du précédent (-)"
  • Le + signifie "un ou plusieurs des précédents ([0-9])"
  • Le $ indique la fin du modèle d'entrée

Ainsi, 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:

156

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]+$ ]]
58
Peter Ho

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.

24
tripleee

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".

9
ephemient

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
8
nortally

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
3
hans

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).

2
Trebor Rude

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 
2
Dennis Williamson

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)

0
Mike Q

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
0
JustinMT

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
0
knipwim