Prueba si la cadena es un entero válido

Estoy tratando de hacer algo bastante común: Analizar la entrada del usuario en un script de shell. Si el usuario proporcionó un número entero válido, el script hace una cosa, y si no es válido, hace algo más. El problema es que no he encontrado una manera fácil (y razonablemente elegante) de hacer esto. No quiero tener que separarlo char por char.

Sé que esto debe ser fácil, pero no sé cómo. Podría hacerlo en una docena de idiomas, ¡pero no en BASH!

En mi investigación encontré esto:

Expresión regular para comprobar si una cadena consta de un número real válido en la base 10

Y hay una respuesta que habla de expresiones regulares, pero hasta donde sé, esa es una función disponible en C (entre otros). Aún así, tenía lo que parecía una gran respuesta, así que lo intenté con grep, pero grep no sabía qué hacer con él. Intenté -P que en mi caja significa tratarlo como una expresión regular PERL – nada. Dash E (-E) tampoco funcionó. Y tampoco lo hizo -F.

Para que quede claro, estoy intentando algo como esto, buscando cualquier salida, a partir de ahí, hackearé el guión para aprovechar cualquier cosa que obtenga. (IOW, esperaba que una entrada no conforme no devuelva nada mientras se repite una línea válida.)

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 

¿Alguien podría ilustrar cómo se hace esto más fácilmente?

Francamente, esta es una breve prueba de TEST, en mi opinión. Debería tener una bandera como esta

 if [ -I "string" ] ; then echo "String is a valid integer." else echo "String is not a valid integer." fi 

 [[ $var =~ ^-?[0-9]+$ ]] 
  • El ^ indica el comienzo del patrón de entrada
  • El - es un literal “-”
  • El ? significa “0 o 1 de los anteriores ( - )”
  • El + significa “1 o más de los precedentes ( [0-9] )”
  • $ Indica el final del patrón de entrada

Así que la expresión regular coincide con un opcional - (para el caso de números negativos), seguido de uno o más dígitos decimales.

Referencias

Wow … ¡hay tantas buenas soluciones aquí! De todas las soluciones anteriores, estoy de acuerdo con @nortally que el uso del -eq one liner es el más genial.

Estoy ejecutando GNU bash, versión 4.1.5 (Debian). También lo he comprobado en ksh (SunSO 5.10).

Aquí está mi versión de verificar si $1 es un número entero o no:

 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 

Este enfoque también da cuenta de los números negativos, que algunas de las otras soluciones tendrán un resultado negativo defectuoso, y permitirá un prefijo de “+” (por ejemplo, +30), que obviamente es un número entero.

Resultados:

 $ 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 solución proporcionada por Ignacio Vázquez-Abrams también fue muy clara (si le gusta la expresión regular) después de haber sido explicada. Sin embargo, no maneja números positivos con el prefijo + , pero se puede arreglar fácilmente de la siguiente manera:

 [[ $var =~ ^[-+]?[0-9]+$ ]] 

Llegó tarde a la fiesta aquí. Estoy extremadamente sorprendido de que ninguna de las respuestas mencione la solución más simple, rápida y portátil; la statement de case .

 case ${variable#[-+]} in *[!0-9]* | '') echo Not a number ;; * ) echo Valid number ;; esac 

El recorte de cualquier signo antes de la comparación se siente como un truco, pero eso hace que la expresión de la statement de caso sea mucho más simple.

Para la portabilidad a pre-Bash 3.1 (cuando se introdujo la prueba =~ ), use 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 busca REGEX anclado al comienzo de STRING, repitiendo el primer grupo (o la duración de la coincidencia, si no hay ninguno) y devuelve éxito / falla. Esta es la vieja syntax de expresiones regulares, de ahí el exceso \ . -\? significa “tal vez - “, [0-9]\+ significa “uno o más dígitos” y $ significa “fin de cadena”.

Bash también es compatible con globos extendidos, aunque no recuerdo de qué versión en adelante.

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

@(-|) significa ” - o nada”, [0-9] significa “dígito”, y *([0-9]) significa “cero o más dígitos”.

Me gusta la solución usando la prueba -eq , porque básicamente es un trazador de líneas.

Mi propia solución fue usar la expansión de parámetros para descartar todos los números y ver si quedaba algo. (Todavía estoy usando 3.0, no he usado [[ o expr antes, pero me alegro de conocerlos).

 if [ "${INPUT_STRING//[0-9]}" = "" ]; then # yes, natural number else # no, has non-numeral chars fi 

Aquí hay otra versión (solo usando el comando de prueba incorporado y su código de retorno):

 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 

Puede pelar sin dígitos y hacer una comparación. Aquí hay un script de demostración:

 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 

Este es el resultado de la prueba:

 44 44 Entero
 -44 44 Entero
 44- 44 No entero
 4-4 44 No entero
 a4 4 No entero
 4a 4 No entero
 .4 4 No entero
 4.4 44 No entero
 -4.4 44 No entero
 09 9 No entero

Para mí, la solución más simple era usar la variable dentro de una expresión (()) , así:

 if ((VAR > 0)) then echo "$VAR is a positive integer." fi 

Por supuesto, esta solución solo es válida si un valor de cero no tiene sentido para su aplicación. Eso sucedió en mi caso, y esto es mucho más simple que las otras soluciones.

Como se señaló en los comentarios, esto puede hacer que esté sujeto a un ataque de ejecución de código: El operador (( )) evalúa VAR , como se indica en la sección Arithmetic Evaluation de la página de comando man bash (1) . Por lo tanto, no debe usar esta técnica cuando la fuente de los contenidos de VAR es incierta (por supuesto, tampoco debe usar CUALQUIER otra forma de expansión variable).

o con 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 

Agregando a la respuesta de Ignacio Vázquez-Abrams. Esto permitirá que el signo + preceda al entero, y permitirá cualquier número de ceros como puntos decimales. Por ejemplo, esto permitirá que +45.00000000 se considere un número entero.
Sin embargo, $ 1 debe estar formateado para contener un punto decimal. 45 no se considera un número entero aquí, pero 45.0 es.

 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 

Para las risas, rápidamente trabajé en un conjunto de funciones para hacer esto (is_string, is_int, is_float, es una cadena alfa, u otra) pero hay formas más eficientes (menos código) para hacer esto:

 #!/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/[^az. ]*//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 } 

Ejecutar algunas pruebas aquí, definí que -44 es un int pero 44- no es 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 

Salida:

 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 = 

NOTA: Los 0’s principales podrían inferir algo más cuando se agreguen números como octal, por lo que sería mejor tirarlos si intenta tratar el ’09’ como un int (que estoy haciendo) (ej. expr 09 + 0 o strip with sed )