Come posso verificare se una variabile è un numero in Bash?


599

Non riesco proprio a capire come posso assicurarmi che un argomento passato al mio script sia un numero o meno.

Tutto quello che voglio fare è qualcosa del genere:

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

Qualsiasi aiuto?


17
A parte questo, l' test && echo "foo" && exit 0 || echo "bar" && exit 1approccio che stai usando potrebbe avere degli effetti collaterali non intenzionali, se l'eco fallisce (forse l'output è verso un FD chiuso), exit 0verrà saltato e il codice proverà a farlo echo "bar". Se fallisce anche in questo, la &&condizione fallirà e non verrà nemmeno eseguita exit 1! L'uso di ifistruzioni effettive piuttosto che &&/ ||è meno soggetto a effetti collaterali imprevisti.
Charles Duffy,

@CharlesDuffy Questo è il tipo di pensiero davvero intelligente che la maggior parte delle persone arriva solo quando deve rintracciare gli insetti pelosi ...! Non ho mai pensato che l'eco potesse restituire un fallimento.
Camilo Martin,

6
Un po 'tardi alla festa, ma conosco i pericoli di cui Charles ha scritto, dato che ho dovuto affrontarli anche qualche tempo fa. Quindi ecco una linea al 100% infallibile (e ben leggibile) per te: [[ $1 =~ "^[0-9]+$" ]] && { echo "number"; exit 0; } || { echo "not a number"; exit 1; }le parentesi graffe indicano che le cose NON dovrebbero essere eseguite in una subshell (che sarebbe sicuramente così con le ()parentesi usate invece). Avvertenza: non perdere mai il punto e virgola finale . Altrimenti potresti causare la bashstampa dei messaggi di errore più brutti (e più inutili) ...
syntaxerror

5
Non funziona in Ubuntu, a meno che non rimuova le virgolette. Quindi dovrebbe essere solo[[ 12345 =~ ^[0-9]+$ ]] && echo OKKK || echo NOOO
Treviño,

4
Dovrai essere più specifico su cosa intendi per "numero" . Un numero intero? Un numero a virgola fissa? Notazione scientifica ("e")? Esiste un intervallo richiesto (ad es. Un valore senza segno a 64 bit) o ​​si consente di scrivere un numero?
Toby Speight,

Risposte:


803

Un approccio consiste nell'utilizzare un'espressione regolare, in questo modo:

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

Se il valore non è necessariamente un numero intero, considerare di modificare la regex in modo appropriato; per esempio:

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

... oppure, per gestire i numeri con un segno:

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

7
+1 per questo approccio, ma fai attenzione ai decimali, eseguendo questo test con, ad esempio, l'errore "1,0" o "1,0" stampa ": non un numero".
sourcerebels,

15
Trovo '' exec> & 2; echo ... '' piuttosto sciocco. Solo '' echo ...> & 2 ''
lhunath,

5
@Ben vuoi davvero gestire più di un segno meno? Lo farei ^-?piuttosto che a ^-*meno che tu non stia effettivamente facendo il lavoro per gestire correttamente più inversioni.
Charles Duffy,

4
@SandraSchlichting Fa sì che tutto l'output futuro vada su stderr. Non è proprio un punto qui, dove c'è solo un'eco, ma è un'abitudine che tendo a prendere in considerazione nei casi in cui i messaggi di errore si estendono su più righe.
Charles Duffy,

29
Non sono sicuro del motivo per cui l'espressione regolare debba essere salvata in una variabile, ma se è per motivi di compatibilità non credo sia necessario. Si potrebbe semplicemente applicare l'espressione direttamente: [[ $yournumber =~ ^[0-9]+$ ]].
konsolebox,

284

Senza bashismi (funziona anche nel System V sh),

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

Questo rifiuta stringhe vuote e stringhe contenenti non cifre, accettando tutto il resto.

I numeri negativi o in virgola mobile richiedono un lavoro aggiuntivo. Un'idea è quella di escludere -/ .nel primo modello "cattivo" e aggiungere altri modelli "cattivi" contenenti gli usi inappropriati di essi ( ?*-*/ *.*.*)


20
+1: questo è un modo idiomatico e portatile di tornare alla shell Bourne originale e ha il supporto integrato per i caratteri jolly in stile glob. Se vieni da un altro linguaggio di programmazione, sembra inquietante, ma è molto più elegante che affrontare la fragilità di vari problemi di quotazione e infiniti problemi di compatibilità con le versioni precedenti / laterali conif test ...
tripleee

6
È possibile modificare la prima riga in ${string#-}(che non funziona nelle antiche shell Bourne, ma funziona in qualsiasi shell POSIX) per accettare numeri interi negativi.
Gilles 'SO- smetti di essere malvagio' il

4
Inoltre, questo è facile da estendere ai float: basta aggiungere '.' | *.*.*i pattern non consentiti e aggiungere un punto ai caratteri consentiti. Allo stesso modo, puoi consentire un segno opzionale prima, anche se poi preferirei case ${string#[-+]}semplicemente ignorare il segno.
Tripleee

Vedi questo per la gestione di numeri interi firmati: stackoverflow.com/a/18620446/2013911
Niklas Peter,

2
@Dor Le virgolette non sono necessarie, poiché il comando case non esegue comunque la suddivisione delle parole e la generazione del percorso su quella parola. (Tuttavia, le espansioni nel caso in cui i motivi possano richiedere un preventivo poiché determinano se i caratteri di corrispondenza dei motivi sono letterali o speciali.)
jilles

193

La seguente soluzione può essere utilizzata anche in shell di base come Bourne senza la necessità di espressioni regolari. Fondamentalmente qualsiasi operazione di valutazione di valore numerico che utilizza non numeri comporterà un errore che sarà implicitamente considerato falso nella shell:

"$var" -eq "$var"

come in:

#!/bin/bash

var=a

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

Puoi anche provare per $? il codice di ritorno dell'operazione che è più esplicito:

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

Il reindirizzamento dell'errore standard è lì per nascondere il messaggio "espressione intera prevista" che bash stampa nel caso in cui non abbiamo un numero.

CAVEATS (grazie ai commenti qui sotto):

  • I numeri con punti decimali non vengono identificati come "numeri" validi
  • Usando [[ ]]invece di [ ]sarà sempre valutatotrue
  • La maggior parte delle shell non Bash valuteranno sempre questa espressione come true
  • Il comportamento in Bash non è documentato e può quindi cambiare senza preavviso
  • Se il valore include spazi dopo il numero (ad es. "1 a") produce errore, ad esempio bash: [[: 1 a: syntax error in expression (error token is "a")
  • Se il valore è uguale a var-name (ad es. I = "i"), produce un errore, come bash: [[: i: expression recursion level exceeded (error token is "i")

8
Lo consiglierei comunque (ma con le variabili citate per consentire stringhe vuote), poiché il risultato è garantito come utilizzabile come numero in Bash, non importa quale.
l0b0,

21
Fare attenzione a utilizzare parentesi singole; [[ a -eq a ]]restituisce true (entrambi gli argomenti vengono convertiti in zero)
Tgr

3
Molto bella! Nota che questo funziona solo per un numero intero, non per un numero qualsiasi. Ho dovuto verificare un singolo argomento che deve essere un numero intero, quindi ha funzionato bene:if ! [ $# -eq 1 -o "$1" -eq "$1" ] 2>/dev/null; then
haridsv

6
Sconsiglio vivamente questo metodo a causa del numero non insignificante di shell il cui [built-in valuterà gli argomenti come aritmetici. Questo è vero sia in ksh93 che in mksh. Inoltre, dal momento che entrambi questi array di supporto, ci sono facili opportunità per l'iniezione di codice. Utilizzare invece una corrispondenza del modello.
ormaaj,

3
@AlbertoZaccagni, nelle attuali versioni di bash, questi valori sono interpretati con regole di contesto numerico solo per [[ ]]ma non per [ ]. Detto questo, questo comportamento non è specificato né dallo standard POSIX testné nella documentazione di bash; le versioni future di bash potrebbero modificare il comportamento in modo che corrisponda a ksh senza infrangere alcuna promessa comportamentale documentata, quindi affidarsi al suo comportamento attuale persistendo non è garantito per essere sicuro.
Charles Duffy,

43

Questo verifica se un numero è un numero intero non negativo ed è sia indipendente dalla shell (cioè senza bashismi) e utilizza solo gli incorporamenti della shell:

NON CORRETTO.

Poiché questa prima risposta (sotto) consente numeri interi con caratteri, purché i primi non siano i primi nella variabile.

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

CORRETTO .

Come ha commentato Jilles e suggerito nella sua risposta, questo è il modo corretto di farlo usando schemi a conchiglia.

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

5
Questo non funziona correttamente, accetta qualsiasi stringa che inizia con una cifra. Nota che WORD in $ {VAR ## WORD} e simili è un modello di shell, non un'espressione regolare.
jilles,

2
Puoi tradurre quell'espressione in inglese, per favore? Voglio davvero usarlo, ma non lo capisco abbastanza da fidarmi, anche dopo aver sfogliato la pagina man di bash.
CivFan,

2
*[!0-9]*è un modello che abbina tutte le stringhe con almeno 1 carattere non cifra. ${num##*[!0-9]*}è un "parametro di espansione" in cui prendiamo il contenuto della numvariabile e rimuoviamo la stringa più lunga che corrisponde al modello. Se il risultato dell'espansione del parametro non è vuoto ( ! [ -z ${...} ]), si tratta di un numero poiché non contiene caratteri non numerici.
mrucci,

Purtroppo questo fallisce se ci sono delle cifre nell'argomento, anche se non è un numero valido. Ad esempio "exam1ple" o "a2b".
Studgeek

Entrambi falliscono con122s :-(
Hastur,

40

Nessuno ha suggerito la corrispondenza estesa del modello di bash :

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

5
Glenn, rimuovo shopt -s extglobdal tuo post (che ho votato, è una delle mie risposte preferite qui), poiché in Costrutti condizionali puoi leggere: Quando vengono utilizzati gli operatori ==e !=, la stringa a destra dell'operatore viene considerata un modello e abbinata secondo le regole descritte di seguito in Pattern Matching , come se l' extglobopzione shell fosse abilitata. Spero non ti dispiaccia!
gniourf_gniourf,

In tali contesti, non è necessario shopt extglob... questa è una buona cosa da sapere!
gniourf_gniourf,

Funziona bene con numeri interi semplici.

2
@Jdamian: hai ragione, questo è stato aggiunto in Bash 4.1 (che è stato rilasciato alla fine del 2009 ... Bash 3.2 è stato rilasciato nel 2006 ... ora è un software antico, mi dispiace per coloro che sono bloccati in passato). Inoltre, potresti obiettare che extglobs è stato introdotto nella versione 2.02 (rilasciata nel 1998) e non funziona nelle versioni <2.02 ... Ora il tuo commento qui servirà da avvertimento per quanto riguarda le versioni precedenti.
gniourf_gniourf

1
Le variabili all'interno [[...]]non sono soggette a suddivisione delle parole o espansione globale.
Glenn Jackman,

26

Sono sorpreso dalle soluzioni che analizzano direttamente i formati numerici nella shell. shell non è adatta a questo, essendo un DSL per il controllo di file e processi. Esistono numerosi parser numerici un po 'più in basso, ad esempio:

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' nel formato particolare che desideri.


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

4
@sputnick la tua versione interrompe la semantica intrinseca (e utile) del valore restituito della funzione originale. Quindi, invece, lascia semplicemente la funzione così com'è e usala:isnumber 23 && echo "this is a number" || echo "not a number"
michael

4
Anche questo non dovrebbe 2>/dev/null, quindi isnumber "foo"non inquina lo stderr?
gioele,

4
Chiamare shell moderne come bash "un DSL per il controllo di file e processi" significa ignorare che vengono utilizzate per molto più di questo - alcune distro ci hanno costruito interi gestori di pacchetti e interfacce Web (per quanto brutti possano essere). Tuttavia, i file batch si adattano alla tua descrizione, poiché anche l'impostazione di una variabile è difficile.
Camilo Martin,

7
È divertente che tu stia cercando di essere intelligente copiando alcuni modi di dire da altre lingue. Sfortunatamente questo non funziona nelle shell. Le conchiglie sono molto speciali e, senza una solida conoscenza di esse, è probabile che tu scriva codice non funzionante. Il tuo codice è rotto: isnumber "'a"tornerà vero. Questo è documentato nelle specifiche POSIX in cui leggerai: Se il carattere principale è una virgoletta singola o una virgoletta doppia, il valore deve essere il valore numerico nel set di codici sottostante del carattere che segue la virgoletta singola o la virgoletta doppia .
gniourf_gniourf,

16

Stavo guardando le risposte e ... ho capito che nessuno pensava ai numeri di FLOAT (con punto)!

Anche usare grep è fantastico.
-E significa regexp esteso
-q significa silenzioso (non fa eco)
-qE è la combinazione di entrambi.

Per testare direttamente nella riga di 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 in uno 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

Per abbinare JUST interi, utilizzare questo:

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

Le soluzioni che utilizzano awk di triple_r e tripleee funzionano con float.
Ken Jackson,

Grazie per questo e ottimo punto! Perché la domanda è in realtà come verificare se si tratta di un numero e non solo di un numero intero.
Tanasis

Ringrazio anche te Tanasis! Aiutiamoci sempre.
Sergio Abreu,

12

Solo un seguito a @mary. Ma poiché non ho abbastanza rappresentante, non posso pubblicare questo come commento a quel post. Comunque, ecco cosa ho usato:

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

La funzione restituirà "1" se l'argomento è un numero, altrimenti restituirà "0". Funziona con numeri interi e float. L'utilizzo è qualcosa del tipo:

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

4
La stampa di un numero è meno utile dell'impostazione di un codice di uscita. 'BEGIN { exit(1-(a==a+0)) }'è leggermente difficile da grok ma può essere utilizzato in una funzione che restituisce vero o falso proprio come [,grep -q e così via
tripleee

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

${i//[0-9]}sostituisce qualsiasi cifra nel valore di $icon una stringa vuota, vedere man -P 'less +/parameter\/' bash. -zcontrolla se la stringa risultante ha lunghezza zero.

se vuoi anche escludere il caso quando $iè vuoto, puoi usare una di queste costruzioni:

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

Complimenti soprattutto per la man -P 'less +/parameter\/' bashparte. Imparare qualcosa di nuovo ogni giorno. :)
David,

@sjas Potresti facilmente aggiungere \-in espressioni regolari per risolvere il problema. Utilizzare [0-9\-\.\+]per tenere conto di numeri float e firmati.
user2683246

@sjas ok, colpa mia
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

Per il mio problema, avevo solo bisogno di assicurarmi che un utente non inserisse accidentalmente del testo, quindi ho cercato di mantenerlo semplice e leggibile

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

Secondo la pagina man, questo fa praticamente quello che voglio

Se il valore dell'espressione è diverso da zero, lo stato di ritorno è 0

Per evitare cattivi messaggi di errore per stringhe che "potrebbero essere numeri", ignoro l'output dell'errore

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

Questo è più simile a isInteger. Ma fantastico grazie!
Naheel,

8

Vecchia domanda, ma volevo solo fissare la mia soluzione. Questo non richiede strani trucchi conchiglia o non si basa su qualcosa che non esiste da sempre.

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

Fondamentalmente rimuove solo tutte le cifre dall'input e se ti rimane una stringa di lunghezza diversa da zero, allora non era un numero.


Questo non riesce per un vuoto var.
gniourf_gniourf,

O per variabili con newline finali o qualcosa del genere $'0\n\n\n1\n\n\n2\n\n\n3\n'.
gniourf_gniourf,

7

Non posso ancora commentare, quindi aggiungerò la mia risposta, che è un'estensione della risposta di Glenn Jackman usando la corrispondenza del modello bash.

Il mio bisogno originale era di identificare numeri e distinguere numeri interi e float. Le definizioni delle funzioni dedotte a:

function isInteger() {
    [[ ${1} == ?(-)+([0-9]) ]]
}

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

Ho usato i test unitari (con shUnit2) per convalidare i miei schemi funzionando come 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

}

Note: il modello isFloat può essere modificato per essere più tollerante rispetto al punto decimale ( @(.,)) e al simbolo E ( @(Ee)). I test delle mie unità testano solo valori interi o float, ma nessun input non valido.


Mi dispiace, non ho capito come si intende utilizzare la funzione di modifica.
3ronco,

6

Vorrei provare questo:

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

Nota: questo riconosce nan e inf come numero.


2
o duplicato o forse più adatto come commento alla risposta di pixelbeat (l'utilizzo %fè probabilmente migliore comunque)
michael

3
Invece di controllare il codice di stato precedente, perché non inserirlo semplicemente if? Questo è quello che iffa ...if printf "%g" "$var" &> /dev/null; then ...
Camilo Martin,

3
Questo ha altri avvertimenti. Convaliderà la stringa vuota e stringhe come 'a.
gniourf_gniourf,

6

Una risposta chiara è già stata data da @charles Dufy e altri. Una soluzione bash pura sarebbe usare il seguente:

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

Sebbene per i numeri reali non sia obbligatorio disporre di un numero prima del punto di radice .

Per fornire un supporto più completo ai numeri fluttuanti e alla notazione scientifica (molti programmi in C / Fortran o altrimenti esporteranno il float in questo modo), un'utile aggiunta a questa riga sarebbe la seguente:

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

In questo modo si ottiene un modo per differenziare i tipi di numero, se si sta cercando un tipo specifico:

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: potremmo elencare i requisiti sintattici per la notazione decimale e scientifica, uno è quello di consentire la virgola come punto radicale, così come ".". Vorremmo quindi affermare che ci deve essere solo uno di questi punti radicali. Ci possono essere due segni +/- in un float [Ee]. Ho imparato alcune altre regole dal lavoro di Aulu e ho testato contro stringhe errate come '' '-' '-E-1' '0-0'. Ecco i miei strumenti regex / substring / expr che sembrano reggere:

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"

Non dimenticare -di includere numeri negativi!


Qual è la versione minima di bash? Ho appena ricevuto bash: bash previsto dall'operatore binario condizionato: errore di sintassi vicino al token imprevisto `= ~ '
Paul Hargreaves,

1
@PaulHargreaves =~esisteva almeno fino alla bash 3.0.
Gilles 'SO- smetti di essere malvagio' il

@PaulHargreaves probabilmente hai avuto un problema con il tuo primo operando, ad esempio troppe virgolette o simili
Joshua Clayton

@JoshuaClayton Ho chiesto informazioni sulla versione perché è molto vecchia bash su una scatola Solaris 7, che abbiamo ancora e non supporta = ~
Paul Hargreaves

6

Ciò può essere ottenuto utilizzando grepper vedere se la variabile in questione corrisponde a un'espressione regolare estesa.

Numero intero di prova 1120:

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

Produzione: Valid number.

Test non intero 1120a:

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

Produzione: Error: not a number.


Spiegazione

  • Il grep, l' -Einterruttore ci permette di utilizzare l'espressione regolare estesa '^[0-9]+$'. Questa espressione regolare indica che la variabile deve []contenere solo i numeri da 0-9zero a nove ^dall'inizio alla $fine della variabile e deve avere almeno +un carattere.
  • Il grep, l' -qinterruttore silenzioso si spegne qualsiasi uscita se sia o non trova nulla.
  • ifcontrolla lo stato di uscita di grep. Lo stato di uscita 0significa successo e qualcosa di più grande significa un errore. Il grepcomando ha uno stato di uscita 0se trova una corrispondenza e 1quando non lo fa;

Quindi mettendo tutto insieme, nel iftest, abbiamo echola variabile $yournumbere la |pipe a grepcui con l' -qinterruttore abbina silenziosamente l' -Eespressione estesa estesa '^[0-9]+$'. Lo stato di uscita grepsarà 0se è grepstata trovata correttamente una corrispondenza e in 1caso contrario. Se siamo riusciti ad abbinare, noi echo "Valid number.". Se non ci riuscissimo, noi echo "Error: not a number.".


Per galleggianti o doppie

Possiamo semplicemente cambiare l'espressione regolare da '^[0-9]+$'a '^[0-9]*\.?[0-9]+$'per float o double.

Galleggiante di prova 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

Produzione: Valid number.

Galleggiante di prova 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

Produzione: Error: not a number.


Per i negativi

Per consentire numeri interi negativi, basta cambiare l'espressione regolare da '^[0-9]+$'a '^\-?[0-9]+$'.

Per consentire float o double negativi, basta cambiare l'espressione regolare da '^[0-9]*\.?[0-9]+$'a '^\-?[0-9]*\.?[0-9]+$'.


Hai ragione, @CharlesDuffy. Grazie. Ho ripulito la risposta.
Joseph Shih,

Giusto sei di nuovo, @CharlesDuffy. Fisso!
Joseph Shih,

1
LGTM; la risposta come modificata ha il mio +1. Le uniche cose che farei diversamente a questo punto sono solo questioni di opinione piuttosto che correttezza (f / e, usare al [-]posto di \-e [.]invece di \.è un po 'più prolisso, ma significa che le tue stringhe non devono cambiare se utilizzato in un contesto in cui le barre rovesciate vengono consumate).
Charles Duffy,

4

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

Puoi anche usare le classi di personaggi di bash.

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

La numerazione includerà lo spazio, il punto decimale e "e" o "E" per il virgola mobile.

Tuttavia, se si specifica un numero esadecimale in stile C, ad esempio "0xffff" o "0XFFFF", [[: digit:]] restituisce true. Un po 'una trappola qui, bash ti permette di fare qualcosa come "0xAZ00" e ancora contarlo come una cifra (non è questo da qualche strana stranezza di compilatori GCC che ti consente di usare la notazione 0x per basi diverse da 16 ??? )

È possibile che si desideri verificare "0x" o "0X" prima di verificare se è un valore numerico se l'input è completamente non attendibile, a meno che non si desideri accettare numeri esadecimali. Ciò sarebbe realizzato da:

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

17
[[ $VAR = *[[:digit:]]* ]]restituirà true se la variabile contiene un numero, non se è un numero intero.
Glenn Jackman,

[[ "z3*&" = *[[:digit:]]* ]] && echo "numeric"stampe numeric. Testato nella versione bash 3.2.25(1)-release.
Jdamian,

1
@ultraswadable, la tua soluzione rileva quelle stringhe contenenti almeno una cifra racchiusa (o meno) da altri caratteri. Ho effettuato il downgrade.
Jdamian,

L'approccio ovviamente corretto è quindi quello di invertire questa [[ -n $VAR && $VAR != *[^[:digit:]]* ]]
tendenza

@eschwartz, la tua soluzione non funziona con numeri negativi
Angel

4

Il modo più semplice è verificare se contiene caratteri non numerici. Sostituisci tutti i caratteri delle cifre con nulla e controlla la lunghezza. Se c'è lunghezza non è un numero.

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

2
Gestire numeri negativi richiederebbe un approccio più complicato.
Andy,

... O un segno positivo opzionale.
Tripleee

@tripleee Mi piacerebbe vedere il tuo approccio se sai come farlo.
Andy,

4

Siccome ho dovuto manometterlo ultimamente e mi piace l' approccio di karttu con l'unità che prova di più. Ho rivisto il codice e aggiunto anche alcune altre soluzioni, provalo tu stesso per vedere i risultati:

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

Quindi isNumber () include trattini, virgole e notazione esponenziale e quindi restituisce VERO su numeri interi e float dove d'altra parte isFloat () restituisce FALSE su valori interi e isInteger () restituisce FALSE anche su float. Per tua comodità, tutto come una fodera:

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 rimuoverei la functionparola chiave in quanto non fa nulla di utile. Inoltre, non sono sicuro dell'utilità dei valori restituiti. Se non diversamente specificato, le funzioni restituiranno lo stato di uscita dell'ultimo comando, quindi non è necessario returnnulla da soli.
Tom Fenech,

Bello, anzi returnsono confusi e lo rendono meno leggibile. Usare le functionparole chiave o meno è più una questione di sapore personale, almeno le ho rimosse da quelle per salvare un po 'di spazio. grazie.
3ronco,

Non dimenticare che i punti e virgola sono necessari dopo i test per le versioni a una riga.
Tom Fenech,

2
isNumber restituirà 'true' su qualsiasi stringa che contiene un numero.
DrStrangepork,

@DrStrangepork In effetti, nel mio array false_values ​​manca quel caso. Lo esaminerò. Grazie per il suggerimento.
3ronco,

4

Uso expr . Restituisce un valore diverso da zero se si tenta di aggiungere uno zero a un valore non numerico:

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

Potrebbe essere possibile usare bc se hai bisogno di numeri non interi, ma non credo bcabbia lo stesso comportamento. L'aggiunta di zero a un non numero porta a zero e restituisce anche un valore pari a zero. Forse puoi combinare bce expr. Utilizzare bcper aggiungere zero a $number. Se la risposta è 0, allora prova expra verificare che $numbernon sia zero.


1
Questo è piuttosto male. Per renderlo leggermente migliore dovresti usare expr -- "$number" + 0; eppure questo lo farà ancora finta 0 isn't a number. Da man expr:Exit status is 0 if EXPRESSION is neither null nor 0, 1 if EXPRESSION is null or 0,
gniourf_gniourf,

3

Mi piace la risposta di Alberto Zaccagni.

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

Prerequisiti importanti: - non vengono generati subshells - non viene richiamato nessun parser RE - la maggior parte delle applicazioni della shell non usano numeri reali

Ma se $varè complesso (ad es. Un accesso di array associativo) e se il numero sarà un numero intero non negativo (la maggior parte dei casi d'uso), allora questo è forse più efficiente?

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

2

Uso printf come altre risposte menzionate, se fornite la stringa di formato "% f" o "% i" printf farà il controllo per voi. Più facile che reinventare i controlli, la sintassi è semplice e breve e la stampa è onnipresente. Quindi secondo me è una buona scelta: puoi anche usare la seguente idea per verificare una serie di cose, non è utile solo per controllare i numeri.

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

espressione regolare vs espansione dei parametri

Come la risposta di Charles Duffy funziona, ma usa solo espressione regolare , e so che questo è lento, vorrei mostrare un altro modo, usando soloespansione dei parametri :

Solo per numeri interi positivi:

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

Per numeri interi:

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

Quindi per il galleggiamento:

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

Per abbinare la richiesta iniziale:

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

Ora un po 'di confronto:

C'è uno stesso controllo basato sulla risposta di Charles Duffy :

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

Alcuni test:

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 va bene. Ora quante volte ci vorrà tutto questo (sul mio lampone pi):

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

Quindi con espressioni regolari:

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

Sono d'accordo, comunque, preferisco non usare regex, quando potrei usare l'espansione dei parametri ... L'abuso di RE renderà più lento lo script bash
F. Hauri,

Risposta modificata: non è necessario extglob(e un po 'più veloce)!
F. Hauri,

@CharlesDuffy, sul mio raspberry pi, 1000 iterazioni impiegheranno 2,5 secondi con la mia versione e 4,4 secondi con la tua versione!
F. Hauri,

Non posso discutere con quello. 👍
Charles Duffy,

1

Per prendere numeri negativi:

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

Inoltre, per prima cosa è necessario abilitare il globbing esteso. Questa è una funzione solo Bash che è disabilitata per impostazione predefinita.
Tripleee

Il globbing esteso di @tripleee si attiva automaticamente quando si utilizza == 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 Grazie per l'aggiornamento. Questo sembra essere un nuovo comportamento che non è vero nel mio Bash 3.2.57 (MacOS Mojave). Vedo che funziona come descritto in 4.4.
triplo

1

Puoi usare "let" anche in questo modo:

[ ~]$ 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
[ ~]$ 

Ma preferisco usare l'operatore "= ~" Bash 3+ come alcune risposte in questo thread.


5
Questo è molto pericoloso. Non valutare l'aritmetica non convalidata nella shell. Prima deve essere convalidato in altro modo.
ormaaj,

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

Rimuovi il -\?modello di corrispondenza in grep se non accetti numeri interi negativi.


2
Downvote per mancanza di spiegazioni. Come funziona? Sembra complesso e fragile, e non è ovvio quali input accetteranno esattamente. (Ad esempio, la rimozione degli spazi è di fondamentale importanza? Perché?
Dirà

1

Ha fatto la stessa cosa qui con un'espressione regolare che verifica l'intera parte e la parte decimale, separata da un punto.

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

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

Potresti spiegare perché la tua risposta è sostanzialmente diversa dalle altre vecchie risposte, ad esempio la risposta di Charles Duffy? Bene, la tua risposta è effettivamente rotta poiché convalida un singolo periodo.
gniourf_gniourf

non sono sicuro di capire il singolo periodo qui ... è previsto uno o zero periodi .... Ma proprio niente di fondamentalmente diverso, ho appena trovato il regex più facile da leggere.
Girolamo

anche l'uso di * dovrebbe corrispondere a casi più reali
Jerome

Il fatto è che stai abbinando la stringa vuota a=''e la stringa che contiene solo un punto, a='.'quindi il tuo codice è un po 'rotto ...
gniourf_gniourf

0

Uso quanto segue (per numeri interi):

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

Quasi corretto (stai accettando la stringa vuota) ma gratificantemente complicato fino al punto di offuscarlo.
Gilles 'SO- smetti di essere malvagio' il

2
Inesatto: stai accettando -n, ecc. (A causa di echo) e stai accettando variabili con newline finali (a causa di $(...)). E a proposito, printnon è un comando shell valido.
gniourf_gniourf,

0

Ho provato la ricetta di ultrasawblade perché mi sembrava la più pratica e non riuscivo a farlo funzionare. Alla fine ho escogitato un altro modo, basato come altri sulla sostituzione dei parametri, questa volta con la sostituzione regex:

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

Rimuove ogni carattere: digit: class in $ var e controlla se ci rimane una stringa vuota, il che significa che l'originale era solo numeri.

Quello che mi piace di questo è il suo ingombro ridotto e la flessibilità. In questa forma funziona solo per numeri interi di base 10 non delimitati, anche se sicuramente puoi usare la corrispondenza dei modelli per adattarla ad altre esigenze.


Leggendo la soluzione di mrucci, sembra quasi la stessa della mia, ma usando la normale sostituzione della corda invece di "sed style". Entrambi usano le stesse regole per la corrispondenza dei motivi e sono, AFAIK, soluzioni intercambiabili.
ata,

sedè POSIX, mentre la soluzione è bash. Entrambi hanno i loro usi
v010dya l'

0

Veloce e sporco: so che non è il modo più elegante, ma di solito ho appena aggiunto uno zero e testare il risultato. così:

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)) restituirà 0 o bomba se $ 1 NON è un numero intero. per esempio:

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: Immagino di non aver mai pensato di cercare float o tipi misti che rendessero l'intera bomba dello script ... nel mio caso, non volevo che andasse oltre. Giocherò con la soluzione di Mrucci e la regex di Duffy: sembrano i più robusti nel framework bash ...


4
Questo accetta espressioni aritmetiche come 1+1, ma rifiuta alcuni numeri interi positivi con le 0s iniziali (perché 08è una costante ottale non valida).
Gilles 'SO- smetti di essere malvagio' il

2
Questo ha anche altre questioni: 0non è un numero, ed è soggetto a iniezione di codice arbitrario, provare: isInteger 'a[$(ls)]'. Ooops.
gniourf_gniourf,

1
E l'espansione di $((...))non è quotata, un numero IFS=123lo cambierà.
Isaac,
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.