¿Cómo pruebo si una variable es un número en Bash?

Simplemente no puedo entender cómo me aseguro de que un argumento pasado a mi script sea un número o no.

Todo lo que quiero hacer es algo como esto:

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

¿Alguna ayuda?

Un enfoque es usar una expresión regular, así:

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

Si el valor no es necesariamente un número entero, considere modificar la expresión regular de forma apropiada; por ejemplo:

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

… o, para manejar números con un signo:

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

Sin bashisms (funciona incluso en el System V sh),

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

Esto rechaza cadenas vacías y cadenas que contienen no dígitos, aceptando todo lo demás.

Los números negativos o de punto flotante necesitan un trabajo adicional. Una idea es excluir - / . en el primer patrón “malo” y agregue más patrones “malos” que contengan los usos inapropiados de ellos ( ?*-* / *.*.* )

La siguiente solución también se puede usar en shells básicos como Bourne sin la necesidad de expresiones regulares. Básicamente, cualquier operación de evaluación de valores numéricos que utilice números que no sean números generará un error que se considerará implícitamente como falso en el shell:

 "$var" -eq "$var" 

como en:

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

Usted también puede probar $? el código de retorno de la operación que es más explícito:

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

La redirección del error estándar está ahí para ocultar el mensaje de “expresión entera esperada” que bash imprime en caso de que no tengamos un número.

CAVEATS (gracias a los comentarios a continuación):

  • Los números con puntos decimales no se identifican como “números” válidos
  • Usar [[ ]] lugar de [ ] siempre evaluará a true
  • La mayoría de las shells que no son Bash siempre evaluarán esta expresión como true
  • El comportamiento en Bash no está documentado y, por lo tanto, puede cambiar sin previo aviso.
  • Si el valor incluye espacios después de que el número (por ejemplo, “1 a”) produce un error, como bash: [[: 1 a: syntax error in expression (error token is "a")
  • Si el valor es el mismo que var-name (por ejemplo, i = “i”), produce un error, como bash: [[: i: expression recursion level exceeded (error token is "i")

Esto prueba si un número es un número entero no negativo y es tanto independiente del shell (es decir, sin bashisms) y solo utiliza integrales de shell:

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

PERO ES INCORRECTO
Como comentó y sugirió Jilles en su respuesta, esta es la forma correcta de hacerlo utilizando patrones de shell.

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

Me sorprenden las soluciones que analizan directamente los formatos numéricos en shell. shell no es adecuado para esto, ya que es una DSL para controlar archivos y procesos. Hay un amplio número de analizadores un poco más abajo, por ejemplo:

 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 } 

Cambia ‘% f’ al formato particular que necesites.

Nadie sugirió la coincidencia de patrones extendidos de bash:

 [[ $1 == ?(-)+([0-9]) ]] && echo "$1 is an integer" 

Estaba mirando las respuestas y … ¡me di cuenta de que nadie pensaba en los números de FLOAT (con punto)!

Usar grep también es genial.
-E significa regexp extendido
-q significa silencioso (no se repite)
-qE es la combinación de ambos.

Para probar directamente en la línea de comando:

 $ 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 

Usando en 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 

Para hacer coincidir JUST enteros, usa esto:

 # change check line to: check=`echo "$1" | grep -E ^\-?[0-9]+$` 

Solo un seguimiento de @mary. Pero como no tengo suficientes representantes, no pude publicar esto como un comentario en esa publicación. De todos modos, aquí está lo que utilicé:

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

La función devolverá “1” si el argumento es un número; de lo contrario, devolverá “0”. Esto funciona tanto para enteros como para flotadores. El uso es algo así como:

 n=-2.05e+07 res=`isnum "$n"` if [ "$res" == "1" ]; then echo "$n is a number" else echo "$n is not a number" fi 

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

También puedes usar las clases de personajes de bash.

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

Los números incluirán el espacio, el punto decimal, y “e” o “E” para el punto flotante.

Pero, si especifica un número hexadecimal estilo C, es decir, “0xffff” o “0XFFFF”, [[: dígito:]] devuelve verdadero. Aquí, un poco de trampa, bash te permite hacer algo como “0xAZ00” y aún contarlo como un dígito (¿no es esto de algún extraño capricho de los comstackdores de GCC que te permiten usar la notación 0x para bases distintas de 16 ??? )

Es posible que desee probar “0x” o “0X” antes de probar si se trata de un valor numérico si su información no es de confianza, a menos que desee aceptar números hexadecimales. Eso se lograría por:

 if [[ ${VARIABLE:1:2} = "0x" ]] || [[ ${VARIABLE:1:2} = "0X" ]]; then echo "$VAR is not numeric"; fi 

Una vieja pregunta, pero solo quería abordar mi solución. Este no requiere ningún truco de shell extraño, o depende de algo que no ha existido para siempre.

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

Básicamente, simplemente elimina todos los dígitos de la entrada, y si te queda una cadena que no es de longitud cero, entonces no era un número.

Intentaré esto:

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

Nota: esto reconoce nan e inf como número.

Todavía no puedo comentar, así que agregaré mi propia respuesta, que es una extensión de la respuesta de Glenn Jackman usando la coincidencia de patrón de bash.

Mi necesidad original era identificar números y distinguir enteros y flotantes. Las definiciones de función deducidas a:

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

Utilicé pruebas unitarias (con shUnit2) para validar que mis patrones funcionaran como estaba previsto:

 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 } 

Notas: El patrón isFloat se puede modificar para que sea más tolerante con respecto al punto decimal ( @(.,) ) Y el símbolo E ( @(Ee) ). Las pruebas de mi unidad solo prueban los valores que son enteros o flotantes, pero ninguna entrada no válida.

 [[ $1 =~ ^-?[0-9]+$ ]] && echo "number" 

¡No olvides incluir números negativos!

Yo uso expr . Devuelve un valor distinto de cero si intenta agregar un cero a un valor no numérico:

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

Podría ser posible utilizar bc si necesita números enteros, pero no creo que bc tenga el mismo comportamiento. Agregar cero a un no número te hace cero y también devuelve un valor de cero. Quizás puedas combinar bc y expr . Use bc para agregar cero a $number . Si la respuesta es 0 , entonces pruebe expr para verificar que $number no sea cero.

 test -z "${i//[0-9]}" && echo digits || echo no no no 

${i//[0-9]} reemplaza cualquier dígito en el valor de $i con una cadena vacía, vea man -P 'less +/parameter\/' bash . -z comprueba si la cadena resultante tiene longitud cero.

si también desea excluir el caso cuando $i está vacío, puede usar una de estas construcciones:

 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 

La forma más simple es verificar si contiene caracteres que no sean dígitos. Reemplaza todos los dígitos sin nada y verifica la longitud. Si hay longitud, no es un número.

 if [[ ! -n ${input//[0-9]/} ]]; then echo "Input Is A Number" fi 

Una respuesta clara ya ha sido dada por @charles Dufy y otros. Una solución de bash pura usaría lo siguiente:

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

Aunque para números reales no es obligatorio tener un número antes del punto de base .

Para proporcionar un soporte más completo de números flotantes y notación científica (muchos progtwigs en C / Fortran o de lo contrario exportarán flotación de esta manera), una adición útil a esta línea sería la siguiente:

 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 

Por lo tanto, conduce a una forma de diferenciar tipos de números, si está buscando un tipo específico:

 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 

Nota: Podríamos enumerar los requisitos sintácticos para la notación decimal y científica, una es permitir la coma como punto de base, así como “.”. Entonces afirmaríamos que debe haber solo un punto de base. Puede haber dos signos +/- en un flotador [Ee]. Aprendí algunas reglas más del trabajo de Aulu y probé con cadenas malas como ” ‘-‘ ‘-E-1’ ‘0-0’. Aquí están mis herramientas regex / substring / expr que parecen estar resistiendo:

 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 

Como tuve que manipular esto últimamente y como el encargado de karttu con la unidad de prueba más. Revisé el código y agregué algunas otras soluciones también, pruébalo tú mismo para ver los resultados:

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

Así que isNumber () incluye guiones, comas y notación exponencial y, por lo tanto, devuelve TRUE en enteros y flotantes donde, por otro lado, isFloat () devuelve FALSE en valores enteros e isInteger () también devuelve FALSE en flotantes. Para su comodidad, todos como una sola línea:

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

Uso lo siguiente (para enteros):

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

Probé la receta de Ultrasawblade ya que me pareció la más práctica y no pude hacerla funcionar. Al final, ideé otra manera, basada en otras en la sustitución de parámetros, esta vez con reemplazo de expresiones regulares:

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

Elimina cada: dígito: carácter de clase en $ var y comprueba si nos queda una cadena vacía, lo que significa que el original era solo números.

Lo que me gusta de este es su pequeña huella y flexibilidad. De esta forma, solo funciona para enteros base 10 no delimitados, aunque seguramente puede usar la coincidencia de patrones para satisfacer otras necesidades.

Rápido y sucio: sé que no es la forma más elegante, pero generalmente le agregué un cero y probé el resultado. al igual que:

 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)) devolverá 0 o bombará si $ 1 NO es un número entero. por ejemplo:

 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 } 

NOTA: Creo que nunca pensé en buscar flotadores o tipos mixtos que hicieran que todo el guión explotara … en mi caso, no quería que fuera más lejos. Voy a jugar con la solución de mrucci y la expresión regular de Duffy: parecen ser las más robustas dentro del marco de bash …

Encontré una versión bastante corta:

 function isnum() { return `echo "$1" | awk -F"\n" '{print ($0 != $0+0)}'` } 
  • variable para verificar

    number=12345 o number=-23234 o number=23.167 o number=-345.234

  • marque numérico o no numérico

    echo $number | grep -E '^-?[0-9]*\.?[0-9]*$' > /dev/null

  • decidir sobre otras acciones basadas en el estado de salida de las anteriores

    if [ $? -eq 0 ]; then echo "Numeric"; else echo "Non-Numeric"; fi

Para atrapar números negativos:

 if [[ $1 == ?(-)+([0-9.]) ]] then echo number else echo not a number fi 

Podría usar “let” también de esta manera:

 [ ~]$ var=1 [ ~]$ let $var && echo "It's a number" || echo "It's not a number" It\'sa number [ ~]$ var=01 [ ~]$ let $var && echo "It's a number" || echo "It's not a number" It\'sa number [ ~]$ var=toto [ ~]$ let $var && echo "It's a number" || echo "It's not a number" It\'s not a number [ ~]$ 

Pero prefiero usar el operador “= ~” Bash 3+ como algunas respuestas en este hilo.

I use printf as other answers mentioned, if you supply the format string “%f” or “%i” printf will do the checking for you. Easier than reinventing the checks, the syntax is simple and short and printf is ubiquitous. So its a decent choice in my opinion – you can also use the following idea to check for a range of things, its not only useful for checking numbers.

 declare -r CHECK_FLOAT="%f" declare -r CHECK_INTEGER="%i" ##  Number - Number to check ##  String - Number type to check ##  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; }

I like Alberto Zaccagni’s answer.

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

Important prerequisites: – no subshells spawned – no RE parsers invoked – most shell applications don’t use real numbers

But if $var is complex (eg an associative array access), and if the number will be a non-negative integer (most use-cases), then this is perhaps more efficient?

 if [ "$var" -ge 0 ] 2> /dev/null; then .. 
 printf '%b' "-123\nABC" | tr '[:space:]' '_' | grep -q '^-\?[[:digit:]]\+$' && echo "Integer." || echo "NOT integer." 

Remove the -\? in grep matching pattern if you don’t accept negative integer.

Following up on David W’s answer from Oct ’13, if using expr this might be better

 test_var=`expr $am_i_numeric \* 0` >/dev/null 2>&1 if [ "$test_var" = "" ] then ...... 

If numeric, multiplied by 1 gives you the same value, (including negative numbers). Otherwise you get null which you can test for

Did the same thing here with a regular expression that test the entire part and decimals part, separated with a dot.

 re="^[0-9]*[.]{0,1}[0-9]*$" if [[ $1 =~ $re ]] then echo "is numeric" else echo "Naahh, not numeric" fi