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


599

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?


17
Como comentario aparte, el test && echo "foo" && exit 0 || echo "bar" && exit 1enfoque que está utilizando puede tener algunos efectos secundarios no deseados: si el eco falla (tal vez la salida es a un FD cerrado), exit 0se omitirá y el código lo intentará echo "bar". Si falla en eso también, la &&condición fallará, ¡y ni siquiera se ejecutará exit 1! El uso de ifdeclaraciones reales en lugar de &&/ ||es menos propenso a efectos secundarios inesperados.
Charles Duffy el

@CharlesDuffy ¡Ese es el tipo de pensamiento realmente inteligente al que la mayoría de las personas solo llegan cuando tienen que localizar bichos peludos ...! Nunca pensé que el eco podría devolver el fracaso.
Camilo Martin

66
Llegué un poco tarde a la fiesta, pero sé sobre los peligros sobre los que Charles escribió, ya que tuve que pasar por ellos hace bastante tiempo también. Así que aquí hay una línea 100% infalible (y bien legible) para usted: [[ $1 =~ "^[0-9]+$" ]] && { echo "number"; exit 0; } || { echo "not a number"; exit 1; }los corchetes indican que las cosas NO deben ejecutarse en una subshell (que definitivamente sería así con ()paréntesis en su lugar). Advertencia: nunca te pierdas el punto y coma final . De lo contrario, podría bashimprimir los mensajes de error más feos (y sin sentido) ...
syntaxerror

55
No funciona en Ubuntu, a menos que no elimine las comillas. Así que debería ser[[ 12345 =~ ^[0-9]+$ ]] && echo OKKK || echo NOOO
Treviño

44
Deberá ser más específico sobre lo que quiere decir con "número" . Un entero? ¿Un número de punto fijo? Notación científica ("e")? ¿Existe un rango requerido (por ejemplo, un valor sin signo de 64 bits) o permite algún número que pueda escribirse?
Toby Speight

Respuestas:


803

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 enmendar la expresión regular adecuadamente; por ejemplo:

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

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

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

77
+1 para este enfoque, pero tenga cuidado con los decimales, haciendo esta prueba con, por ejemplo, "1.0" o "1.0" imprime el error "No es un número".
sourcerebels

15
Encuentro el '' exec> & 2; echo ... '' bastante tonto. Sólo '' eco ...> & 2 ''
lhunath

55
@Ben, ¿realmente quieres manejar más de un signo menos? Lo haría en ^-?lugar de a ^-*menos que realmente estés haciendo el trabajo para manejar múltiples inversiones correctamente.
Charles Duffy

44
@SandraSchlichting Hace que toda la producción futura vaya a stderr. No es realmente un punto aquí, donde solo hay un eco, pero es un hábito en el que tiendo a incursionar en los casos en que los mensajes de error abarcan varias líneas.
Charles Duffy

29
No estoy seguro de por qué la expresión regular debe guardarse en una variable, pero si es por razones de compatibilidad, no creo que sea necesario. Se podía aplicar directamente la expresión: [[ $yournumber =~ ^[0-9]+$ ]].
konsolebox

284

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

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

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

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


20
+1: es una forma idiomática y portátil de regreso al shell Bourne original, y tiene soporte incorporado para comodines de estilo glob. Si vienes de otro lenguaje de programación, parece extraño, pero es mucho más elegante que lidiar con la fragilidad de varios problemas de if test ...
citas

66
Puede cambiar la primera línea a ${string#-}(que no funciona en conchas Bourne antiguas, pero funciona en cualquier shell POSIX) para aceptar enteros negativos.
Gilles 'SO- deja de ser malvado'

44
Además, esto es fácil de extender a los flotadores: solo agregue '.' | *.*.*a los patrones no permitidos y agregue puntos a los caracteres permitidos. Del mismo modo, puede permitir un signo opcional antes, aunque entonces preferiría case ${string#[-+]}simplemente ignorar el signo.
tripleee

Vea esto para manejar enteros firmados: stackoverflow.com/a/18620446/2013911
Niklas Peter

2
@Dor Las comillas no son necesarias, ya que el comando mayúsculas y minúsculas no realiza la división de palabras y la generación de nombres de ruta en esa palabra de todos modos. (Sin embargo, las expansiones en los patrones de casos pueden necesitar citando ya que determina si los caracteres de coincidencia de patrones son literales o especial.)
Jilles

193

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 valor numérico que utilice números que no sean números dará como resultado 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

También puedes probar por $? 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.

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

  • Los números con puntos decimales no se identifican como "números" válidos
  • Usar en [[ ]]lugar de [ ]siempre evaluará atrue
  • La mayoría de los 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 del 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 el nombre var (por ejemplo, i = "i"), produce un error, como bash: [[: i: expression recursion level exceeded (error token is "i")

8
Todavía lo recomendaría (pero con las variables citadas para permitir cadenas vacías), ya que el resultado está garantizado para ser utilizado como un número en Bash, pase lo que pase.
l0b0

21
Tenga cuidado de usar corchetes individuales; [[ a -eq a ]]se evalúa como verdadero (ambos argumentos se convierten a cero)
Tgr

3
¡Muy agradable! Tenga en cuenta que esto solo funciona para un número entero, no para cualquier número. Necesitaba verificar un solo argumento que debe ser un número entero, por lo que funcionó bien:if ! [ $# -eq 1 -o "$1" -eq "$1" ] 2>/dev/null; then
haridsv

66
Aconsejaría encarecidamente este método debido al número no insignificante de shells cuyo valor [incorporado evaluará los argumentos como aritméticos. Eso es cierto tanto en ksh93 como en mksh. Además, dado que ambas matrices de soporte, hay una oportunidad fácil para la inyección de código. Use una coincidencia de patrón en su lugar.
ormaaj

3
@AlbertoZaccagni, en las versiones actuales de bash, estos valores se interpretan con reglas de contexto numérico solo para [[ ]]pero no para [ ]. Dicho esto, este comportamiento no está especificado por el estándar POSIX para testy en la propia documentación de bash; versiones futuras de bash podrían modificar el comportamiento para que coincida con ksh sin romper ninguna promesa de comportamiento documentada, por lo que no se garantiza que depender de su comportamiento actual persistente sea seguro.
Charles Duffy

43

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

INCORRECTO.

Como esta primera respuesta (a continuación) permite enteros con caracteres en ellos, siempre que los primeros no sean los primeros en la variable.

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

CORRECTO .

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

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

55
Esto no funciona correctamente, acepta cualquier cadena que comience con un dígito. Tenga en cuenta que WORD en $ {VAR ## WORD} y similar es un patrón de shell, no una expresión regular.
jilles

2
¿Puedes traducir esa expresión al inglés, por favor? Realmente quiero usarlo, pero no lo entiendo lo suficiente como para confiar en él, incluso después de leer la página de manual de bash.
CivFan

2
*[!0-9]*es un patrón que coincide con todas las cadenas con al menos 1 carácter sin dígitos. ${num##*[!0-9]*}es una "expansión de parámetros" donde tomamos el contenido de la numvariable y eliminamos la cadena más larga que coincide con el patrón. Si el resultado de la expansión del parámetro no está vacío ( ! [ -z ${...} ]), entonces es un número, ya que no contiene ningún carácter que no sea un dígito.
mrucci

Desafortunadamente, esto falla si hay dígitos en el argumento, incluso si no es un número válido. Por ejemplo "exam1ple" o "a2b".
studgeek

Ambos fallan con122s :-(
Hastur

40

Nadie sugirió la coincidencia de patrones extendida de bash :

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

55
Glenn, shopt -s extglobelimino de tu publicación (que he votado, es una de mis respuestas favoritas aquí), ya que en Construcciones condicionales puedes leer: Cuando se usan los operadores ==y !=, la cadena a la derecha del operador se considera un patrón y coincide de acuerdo con las reglas descritas a continuación en Pattern Matching , como si la extglobopción de shell estuviera habilitada. ¡Espero que no te moleste!
gniourf_gniourf

En tales contextos, no es necesario shopt extglob... ¡es bueno saberlo!
gniourf_gniourf

Funciona bien para enteros simples.

2
@Jdamian: tienes razón, esto se agregó en Bash 4.1 (que se lanzó a finales de 2009 ... Bash 3.2 se lanzó en 2006 ... ahora es un software antiguo, lo siento por aquellos que están atrapados en el pasado). Además, podría argumentar que extglobs se introdujo en la versión 2.02 (lanzada en 1998), y no funciona en versiones <2.02 ... Ahora su comentario aquí servirá como una advertencia con respecto a las versiones anteriores.
gniourf_gniourf

1
Las variables dentro [[...]]no están sujetas a división de palabras o expansión global.
Glenn Jackman

26

Estoy sorprendido por las soluciones que analizan directamente los formatos de números en shell. Shell no se adapta bien a esto, ya que es un DSL para controlar archivos y procesos. Hay numerosos analizadores numéricos 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
}

Cambie '% f' al formato particular que requiera.


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

44
@sputnick su versión rompe la semántica de valor de retorno inherente (y útil) de la función original. Por lo tanto, simplemente deje la función como está y úsela:isnumber 23 && echo "this is a number" || echo "not a number"
michael

44
¿No debería tener esto también 2>/dev/null, para que isnumber "foo"no contamine stderr?
gioele

44
Llamar shells modernos como bash "un DSL para controlar archivos y procesos" es ignorar que se usan para mucho más que eso: algunas distribuciones han construido administradores de paquetes completos e interfaces web (por muy feo que sea). Sin embargo, los archivos por lotes se ajustan a su descripción, ya que incluso establecer una variable allí es difícil.
Camilo Martin

77
Es curioso que intentes ser inteligente copiando algunas expresiones idiomáticas de otros idiomas. Lamentablemente, esto no funciona en los depósitos. Los shells son muy especiales, y sin un conocimiento sólido sobre ellos, es probable que escriba código roto. Su código está roto: isnumber "'a"devolverá verdadero. Esto está documentado en la especificación POSIX donde leerá: Si el carácter inicial es una comilla simple o doble, el valor será el valor numérico en el conjunto de códigos subyacente del carácter que sigue a la comilla simple o doble .
gniourf_gniourf

16

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

Usar grep también es genial.
-E significa expresión regular extendida
-q significa silencio (no hace eco)
-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

Utilizando 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 enteros JUST, use esto:

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

Las soluciones que utilizan awk de triple_r y tripleee funcionan con flotadores.
Ken Jackson

Gracias por esto y muy buen punto! Porque la pregunta es en realidad cómo verificar si es un número y no solo un número entero.
Tanasis

Yo también te agradezco Tanasis! Ayudémonos unos a otros siempre.
Sergio Abreu

12

Solo un seguimiento a @mary. Pero debido a que no tengo suficiente representante, no pude publicar esto como un comentario a esa publicación. De todos modos, esto es lo que usé:

isnum() { awk -v a="$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 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

44
Imprimir un número es menos útil que configurar un código de salida. 'BEGIN { exit(1-(a==a+0)) }'es un poco difícil de asimilar pero se puede utilizar en una función que devuelve verdadero o falso como [, grep -q, etc.
tripleee

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

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

si también desea excluir el caso cuando $iestá 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

Pulgares arriba especialmente para la man -P 'less +/parameter\/' bashparte. Aprendiendo algo nuevo cada día. :)
David

@sjas Puede agregar fácilmente \-expresiones regulares para abordar el problema. Úselo [0-9\-\.\+]para contabilizar flotadores y números firmados.
user2683246

@sjas ok, mi culpa
user2683246

@sjasecho $i | python -c $'import sys\ntry:\n float(sys.stdin.read().rstrip())\nexcept:\n sys.exit(1)' && echo yes || echo no
user2683246

9

Para mi problema, solo necesitaba asegurarme de que un usuario no ingresara accidentalmente algún texto, por lo que traté de mantenerlo simple y legible

isNumber() {
    (( $1 )) 2>/dev/null
}

De acuerdo con la página del manual, esto hace más o menos lo que quiero

Si el valor de la expresión no es cero, el estado de retorno es 0

Para evitar mensajes de error desagradables para cadenas que "podrían ser números", ignoro la salida de error

$ (( 2s ))
bash: ((: 2s: value too great for base (error token is "2s")

Esto es más como isInteger. Pero increíble gracias!
Naheel

8

Antigua pregunta, pero solo quería agregar mi solución. Este no requiere ningún truco extraño de shell, ni depende de algo que no ha existido desde siempre.

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

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


Esto falla por un vacío var.
gniourf_gniourf

O para variables con líneas nuevas o algo así $'0\n\n\n1\n\n\n2\n\n\n3\n'.
gniourf_gniourf

7

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

Mi necesidad original era identificar números y distinguir enteros y flotantes. Las definiciones de funciones 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 funcionaron según lo 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 el punto decimal ( @(.,)) y el símbolo E ( @(Ee)). Las pruebas de mi unidad solo prueban valores que son enteros o flotantes, pero no una entrada no válida.


Lo siento, no entendí cómo se pretende utilizar la función de edición.
3ronco

6

Yo intentaría 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.


2
ya sea duplicado de, o tal vez más adecuado como comentario para, la respuesta de pixelbeat (el uso %fes probablemente mejor de todos modos)
michael

3
En lugar de verificar el código de estado anterior, ¿por qué no simplemente ponerlo en ifsí mismo? Eso es lo ifque ...if printf "%g" "$var" &> /dev/null; then ...
Camilo Martin

3
Esto tiene otras advertencias. Validará la cadena vacía y cadenas como 'a.
gniourf_gniourf

6

@Charles Dufy y otros ya han dado una respuesta clara. Una solución 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 los números reales no es obligatorio tener un número antes del punto radix .

Para proporcionar un soporte más completo de números flotantes y notación científica (muchos programas en C / Fortran o exportarán flotantes 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 los tipos de números, si está buscando algún 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, uno de ellos para permitir la coma como punto de raíz, así como ".". Entonces afirmaríamos que debe haber solo uno de esos puntos radix. Puede haber dos signos +/- en un flotador [Ee]. Aprendí algunas reglas más del trabajo de Aulu, y probé contra 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

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

¡No olvides -incluir números negativos!


¿Cuál es la versión mínima de bash? Acabo de recibir bash: operador binario condicional esperado bash: error de sintaxis cerca de token inesperado `= ~ '
Paul Hargreaves

1
@PaulHargreaves =~existió al menos desde Bash 3.0.
Gilles 'SO- deja de ser malvado'

@PaulHargreaves probablemente haya tenido un problema con su primer operando, por ejemplo, demasiadas comillas o similar
Joshua Clayton

@JoshuaClayton Pregunté acerca de la versión porque es una fiesta muy muy antigua en una caja de Solaris 7, que todavía tenemos y no es compatible = ~
Paul Hargreaves

6

Esto se puede lograr usando greppara ver si la variable en cuestión coincide con una expresión regular extendida.

Prueba de entero 1120:

yournumber=1120
if echo "$yournumber" | grep -qE '^[0-9]+$'; then
    echo "Valid number."
else
    echo "Error: not a number."
fi

Salida: Valid number.

Prueba no entera 1120a:

yournumber=1120a
if echo "$yournumber" | grep -qE '^[0-9]+$'; then
    echo "Valid number."
else
    echo "Error: not a number."
fi

Salida: Error: not a number.


Explicación

  • El grep, el -Einterruptor nos permite usar expresiones regulares extendidas '^[0-9]+$'. Esta expresión regular significa que la variable solo debe []contener los números del 0-9cero al nueve desde el ^principio hasta el $final de la variable y debe tener al menos +un carácter.
  • El grep, el -qinterruptor de silencio se apaga cualquier salida si es o no encuentra nada.
  • ifcomprueba el estado de salida de grep. El estado de salida 0significa éxito y cualquier cosa mayor significa un error. El grepcomando tiene un estado de salida de 0si encuentra una coincidencia y 1cuándo no;

Entonces, al ponerlo todo junto, en la ifprueba, hacemos echola variable $yournumbery la |canalizamos a la grepque con el -qinterruptor coincide silenciosamente con la -Eexpresión de '^[0-9]+$'expresión regular extendida . El estado de salida de grepserá 0si grepse encuentra con éxito una coincidencia y 1si no. Si lo conseguimos, nosotros echo "Valid number.". Si no coincide, nosotros echo "Error: not a number.".


Para carrozas o dobles

Simplemente podemos cambiar la expresión regular de '^[0-9]+$'a '^[0-9]*\.?[0-9]+$'para flotantes o dobles.

Prueba de flotación 1120.01:

yournumber=1120.01
if echo "$yournumber" | grep -qE '^[0-9]*\.?[0-9]+$'; then
    echo "Valid number."
else
    echo "Error: not a number."
fi

Salida: Valid number.

Prueba de flotación 11.20.01:

yournumber=11.20.01
if echo "$yournumber" | grep -qE '^[0-9]*\.?[0-9]+$'; then
    echo "Valid number."
else
    echo "Error: not a number."
fi

Salida: Error: not a number.


Para negativos

Para permitir enteros negativos, simplemente cambie la expresión regular de '^[0-9]+$'a '^\-?[0-9]+$'.

Para permitir flotantes o dobles negativos, simplemente cambie la expresión regular de '^[0-9]*\.?[0-9]+$'a '^\-?[0-9]*\.?[0-9]+$'.


Tienes razón, @CharlesDuffy. Gracias. Limpié la respuesta.
Joseph Shih

Correcto, tienes razón, @CharlesDuffy. ¡Fijo!
Joseph Shih

1
LGTM; La respuesta como editada tiene mi +1. Las únicas cosas que haría de manera diferente en este punto son solo cuestiones de opinión en lugar de corrección (f / e, usar en [-]lugar de \-y en [.]lugar de \.es un poco más detallado, pero significa que sus cadenas no tienen que cambiar si ' Reutilizado en un contexto donde las barras invertidas se consumen).
Charles Duffy

4

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 espacio, el punto decimal y "e" o "E" para coma flotante.

Pero, si especifica un número hexadecimal de estilo C, es decir, "0xffff" o "0XFFFF", [[: dígito:]] devuelve verdadero. Un poco de trampa aquí, bash te permite hacer algo como "0xAZ00" y aún contarlo como un dígito (¿no es esto de alguna peculiaridad extraña de los compiladores 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 es un valor numérico si su entrada no es de confianza, a menos que desee aceptar números hexadecimales. Eso se lograría mediante:

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

17
[[ $VAR = *[[:digit:]]* ]]devolverá verdadero si la variable contiene un número, no si es un número entero.
Glenn Jackman

[[ "z3*&" = *[[:digit:]]* ]] && echo "numeric"impresiones numeric. Probado en la versión bash 3.2.25(1)-release.
Jdamian

1
@ultraswadable, su solución detecta aquellas cadenas que contienen, al menos, un dígito rodeado (o no) por cualquier otro carácter. Yo voté en contra.
Jdamian

El enfoque obviamente correcto es, por lo tanto, revertir esto, y usar[[ -n $VAR && $VAR != *[^[:digit:]]* ]]
eschwartz

@eschwartz, su solución no funciona con números negativos
Angel

4

La forma más simple es verificar si contiene caracteres que no sean dígitos. Reemplaza todos los caracteres de dígitos con 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

2
Manejar números negativos requeriría un enfoque más complicado.
Andy

... O un signo positivo opcional.
tripleee

@tripleee, me gustaría ver tu enfoque si sabes cómo hacerlo.
Andy

4

Como tuve que manipular esto últimamente y me gusta enfoque de karttu con la prueba de la unidad más. Revisé el código y agregué algunas otras soluciones también, pruébelo usted 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

Entonces isNumber () incluye guiones, comas y notación exponencial y, por lo tanto, devuelve TRUE en enteros y flotantes, mientras que isFloat () devuelve FALSE en valores enteros y isInteger () también devuelve FALSE en flotantes. Para su conveniencia, todo en uno:

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

Personalmente, eliminaría la functionpalabra clave ya que no hace nada útil. Además, no estoy seguro de la utilidad de los valores de retorno. A menos que se especifique lo contrario, las funciones devolverán el estado de salida del último comando, por lo que no necesita hacer returnnada usted mismo.
Tom Fenech

Agradable, de hecho, los returns son confusos y lo hacen menos legible. Usar functionpalabras clave o no es más una cuestión de sabor personal, al menos las eliminé de los revestimientos para ahorrar espacio. gracias.
3ronco

No olvide que se necesitan punto y coma después de las pruebas para las versiones de una línea.
Tom Fenech

2
isNumber devolverá 'verdadero' en cualquier cadena que tenga un número.
DrStrangepork

@DrStrangepork De hecho, mi matriz false_values ​​no tiene ese caso. Tendré que investigarlo. Gracias por la pista.
3ronco

4

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 usar bc si necesita números no enteros, pero no creo que bctenga el mismo comportamiento. Agregar cero a un no número te da cero y también devuelve un valor de cero. Quizás puedas combinar bcy expr. Use bcpara agregar cero a $number. Si la respuesta es 0, intente exprverificar que $numberno sea cero.


1
Esto es bastante malo. Para hacerlo un poco mejor debes usar expr -- "$number" + 0; sin embargo, esto seguirá pretendiendo eso 0 isn't a number. De man expr:Exit status is 0 if EXPRESSION is neither null nor 0, 1 if EXPRESSION is null or 0,
gniourf_gniourf

3

Me gusta la respuesta de Alberto Zaccagni.

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

Requisitos previos importantes: - no se generan subcapas - no se invocan analizadores RE - la mayoría de las aplicaciones de shell no usan números reales

Pero si $vares complejo (por ejemplo, un acceso de matriz asociativo), y si el número será un número entero no negativo (la mayoría de los casos de uso), ¿entonces esto es quizás más eficiente?

if [ "$var" -ge 0 ] 2> /dev/null; then ..

2

Utilizo printf como se menciona en otras respuestas, si proporciona la cadena de formato "% f" o "% i" printf hará la comprobación por usted. Más fácil que reinventar los cheques, la sintaxis es simple y corta y printf es omnipresente. Por lo tanto, es una opción decente en mi opinión: también puede usar la siguiente idea para verificar una variedad de cosas, no solo es útil para verificar números.

declare  -r CHECK_FLOAT="%f"  
declare  -r CHECK_INTEGER="%i"  

 ## <arg 1> Number - Number to check  
 ## <arg 2> String - Number type to check  
 ## <arg 3> 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; }


2

expresión regular vs expansión de parámetros

Como la respuesta de Charles Duffy funciona, pero solo usaexpresión regular , y sé que esto es lento, me gustaría mostrar otra forma, usando soloexpansión de parámetros :

Solo para enteros positivos:

is_num() { [ "$1" ] && [ -z "${1//[0-9]}" ] ;}

Para enteros:

is_num() { 
    local chk=${1#[+-]};
    [ "$chk" ] && [ -z "${chk//[0-9]}" ]
}

Luego para flotar:

is_num() { 
    local chk=${1#[+-]};
    chk=${chk/.}
    [ "$chk" ] && [ -z "${chk//[0-9]}" ]
}

Para que coincida con la solicitud inicial:

set -- "foo bar"
is_num "$1" && VAR=$1 || echo "need a number"
need a number

set -- "+3.141592"
is_num "$1" && VAR=$1 || echo "need a number"

echo $VAR
+3.141592

Ahora una pequeña comparación:

Hay un mismo control basado en la respuesta de Charles Duffy :

cdIs_num() { 
    local re='^[+-]?[0-9]+([.][0-9]+)?$';
    [[ $1 =~ $re ]]
}

Algunas pruebas:

if is_num foo;then echo It\'s a number ;else echo Not a number;fi
Not a number
if cdIs_num foo;then echo It\'s a number ;else echo Not a number;fi
Not a number
if is_num 25;then echo It\'s a number ;else echo Not a number;fi
It's a number
if cdIs_num 25;then echo It\'s a number ;else echo Not a number;fi
It's a number
if is_num 3+4;then echo It\'s a number ;else echo Not a number;fi
Not a number
if cdIs_num 3+4;then echo It\'s a number ;else echo Not a number;fi
Not a number
if is_num 3.1415;then echo It\'s a number ;else echo Not a number;fi
It's a number
if cdIs_num 3.1415;then echo It\'s a number ;else echo Not a number;fi
It's a number

OK eso está bien. Ahora, ¿cuánto tiempo llevará todo esto (en mi frambuesa pi):

time for i in {1..1000};do is_num +3.14159265;done
real    0m2.476s
user    0m1.235s
sys     0m0.000s

Luego con expresiones regulares:

time for i in {1..1000};do cdIs_num +3.14159265;done
real    0m4.363s
user    0m2.168s
sys     0m0.000s

Estoy de acuerdo, de todos modos, prefiero no usar expresiones regulares, cuando podría usar la expansión de parámetros ... El abuso de RE hará que el script bash sea más lento
F. Hauri

Respuesta editada: ¡No es necesario extglob(y un poco más rápido)!
F. Hauri

@CharlesDuffy, en mi frambuesa pi, ¡1000 iteraciones tomarán 2,5 segundos con mi versión y 4,4 segundos con tu versión!
F. Hauri

No puedo discutir con eso. 👍
Charles Duffy

1

Para atrapar números negativos:

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

Además, esto requiere un globbing extendido para habilitarse primero. Esta es una característica de solo Bash que está deshabilitada de manera predeterminada.
tripleee

@tripleee Extended Globbing se activa automáticamente cuando se usa == o! = When the ‘==’ and ‘!=’ operators are used, the string to the right of the operator is considered a pattern and matched according to the rules described below in Pattern Matching, as if the extglob shell option were enabled. gnu.org/software/bash/manual/bashref.html#index-_005b_005b
Badr Elmers

@BadrElmers Gracias por la actualización. Este parece ser un nuevo comportamiento que no es cierto en mi Bash 3.2.57 (MacOS Mojave). Veo que funciona como lo describe en 4.4.
tripleee

1

También puedes usar "let" así:

[ ~]$ var=1
[ ~]$ let $var && echo "It's a number" || echo "It's not a number"
It\'s a number
[ ~]$ var=01
[ ~]$ let $var && echo "It's a number" || echo "It's not a number"
It\'s a 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.


55
Esto es muy peligroso No evalúe la aritmética no validada en el shell. Debe validarse de alguna otra manera primero.
ormaaj

1
printf '%b' "-123\nABC" | tr '[:space:]' '_' | grep -q '^-\?[[:digit:]]\+$' && echo "Integer." || echo "NOT integer."

Elimine el -\?patrón de coincidencia in grep si no acepta un entero negativo.


2
Voto negativo por falta de explicación. ¿Como funciona esto? Parece complejo y quebradizo, y no es obvio qué entradas aceptará exactamente. (Por ejemplo, es la eliminación de espacios crucialmente necesaria por qué va a decir un número con espacios incrustados es un número válido, que puede no ser deseable??.)
tripleee

1

Hizo lo mismo aquí con una expresión regular que prueba la parte completa y la parte de decimales, separadas con un punto.

re="^[0-9]*[.]{0,1}[0-9]*$"

if [[ $1 =~ $re ]] 
then
   echo "is numeric"
else
  echo "Naahh, not numeric"
fi

¿Podría explicar por qué su respuesta es fundamentalmente diferente de otras respuestas antiguas, por ejemplo, la respuesta de Charles Duffy? Bueno, su respuesta en realidad está rota ya que valida un solo período.
gniourf_gniourf

no estoy seguro de entender el período único aquí ... es uno o cero períodos esperados ... Pero no hay nada fundamentalmente diferente, solo encontré la expresión regular más fácil de leer.
Jerome

también usando * debería coincidir con más casos del mundo real
Jerome

La cosa es que estás haciendo coincidir la cadena vacía a=''y la cadena que contiene un punto solo a='.'para que tu código esté un poco roto ...
gniourf_gniourf

0

Yo 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

Casi correcto (estás aceptando la cadena vacía) pero gratificantemente complicado hasta el punto de ofuscación.
Gilles 'SO- deja de ser malvado'

2
Incorrecto: está aceptando -n, etc. (debido a echo), y está aceptando variables con nuevas líneas finales (debido a $(...)). Y, por cierto, printno es un comando de shell válido.
gniourf_gniourf

0

Probé la receta de ultrasawblade porque me pareció la más práctica y no pude hacer que funcionara. Al final, ideé otra forma, basada en otros 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 carácter: dígito: clase en $ var y comprueba si nos queda una cadena vacía, lo que significa que el original eran solo números.

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


Leyendo la solución de mrucci, se ve casi igual que la mía, pero usando un reemplazo de cadena regular en lugar de "estilo sed". Ambos usan las mismas reglas para la coincidencia de patrones y son, AFAIK, soluciones intercambiables.
ata

sedes POSIX, mientras que su solución es bash. Ambos tienen sus usos
v010dya

0

Rápido y sucio: Sé que no es la forma más elegante, pero generalmente solo 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 bombardeará 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: Supongo que nunca pensé en buscar flotadores o tipos mixtos que hicieran que todo el guión de la bomba ... en mi caso, no quería que fuera más allá. Voy a jugar con la solución de mrucci y la expresión regular de Duffy: parecen las más robustas dentro del marco de bash ...


44
Esto acepta expresiones aritméticas como 1+1, pero rechaza algunos enteros positivos con 0s iniciales (porque 08es una constante octal no válida).
Gilles 'SO- deja de ser malvado'

2
Esto tiene también otras cuestiones: 0no es un número, y que está sujeta a la inyección de código arbitrario, probarlo: isInteger 'a[$(ls)]'. Ooops
gniourf_gniourf

1
Y la expansión de $((...))no se cita, un número IFS=123lo cambiará.
Isaac
Al usar nuestro sitio, usted reconoce que ha leído y comprende nuestra Política de Cookies y Política de Privacidad.
Licensed under cc by-sa 3.0 with attribution required.