894 Stimmen

Wie kann ich in der Bash testen, ob eine Variable eine Zahl ist?

Ich kann einfach nicht herausfinden, wie ich sicherstellen kann, dass ein an mein Skript übergebenes Argument eine Zahl ist oder nicht.

Alles, was ich tun möchte, ist so etwas wie das hier:

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

Gibt es Hilfe?

21 Stimmen

Nebenbei bemerkt - die test && echo "foo" && exit 0 || echo "bar" && exit 1 Ansatz, den Sie verwenden, kann einige unbeabsichtigte Nebeneffekte haben -- wenn das Echo fehlschlägt (vielleicht ist die Ausgabe an einen geschlossenen FD), wird die exit 0 wird übersprungen, und der Code wird dann versuchen echo "bar" . Scheitert sie auch daran, wird die && wird fehlschlagen, und es wird nicht einmal ausgeführt exit 1 ! Mit aktuellen if Erklärungen und nicht && / || ist weniger anfällig für unerwartete Nebenwirkungen.

0 Stimmen

@CharlesDuffy Das ist die Art von wirklich cleverem Denken, zu dem die meisten Leute nur kommen, wenn sie haarige Bugs aufspüren müssen...! Ich hätte nie gedacht, dass Echo einen Fehler zurückgeben könnte.

13 Stimmen

Ich bin zwar etwas spät dran, aber ich weiß um die Gefahren, über die Charles geschrieben hat, da ich sie vor einiger Zeit auch durchmachen musste. Hier ist also eine 100 % narrensichere (und gut lesbare) Zeile für Sie: [[ $1 =~ "^[0-9]+$" ]] && { echo "number"; exit 0; } || { echo "not a number"; exit 1; } Die geschweiften Klammern zeigen an, dass die Dinge NICHT in einer Subshell ausgeführt werden sollen (was bei () stattdessen Klammern verwenden). Vorbehalt: Niemals das letzte Semikolon vergessen . Andernfalls könnten Sie bash um die hässlichsten (und sinnlosesten) Fehlermeldungen auszugeben...

1154voto

Charles Duffy Punkte 255246

Eine Möglichkeit ist die Verwendung eines regulären Ausdrucks, etwa so:

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

Wenn der Wert nicht notwendigerweise eine ganze Zahl ist, sollten Sie die Regex entsprechend abändern, zum Beispiel:

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

...oder, um Zahlen mit einem Vorzeichen zu behandeln:

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

414voto

jilles Punkte 9914

Ohne Bashismen (funktioniert sogar im System V sh),

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

Damit werden leere Zeichenketten und Zeichenketten, die keine Ziffern enthalten, zurückgewiesen, alles andere wird akzeptiert.

Negative Zahlen oder Gleitkommazahlen erfordern zusätzliche Arbeit. Eine Idee ist, Folgendes auszuschließen - / . im ersten "schlechten" Muster und fügen Sie weitere "schlechte" Muster hinzu, die die unangemessenen Verwendungen dieser Muster enthalten ( ?*-* / *.*.* )

272voto

Alberto Zaccagni Punkte 29518

Die folgende Lösung kann auch in einfachen Shells wie Bourne verwendet werden, ohne dass reguläre Ausdrücke erforderlich sind. Grundsätzlich führen alle Operationen zur Auswertung numerischer Werte, die keine Zahlen enthalten, zu einem Fehler, der in der Shell implizit als falsch angesehen wird:

"$var" -eq "$var"

wie in:

#!/bin/bash

var=a

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

Sie können auch auf $?, den Rückgabecode der Operation, testen, was eindeutiger ist:

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

Die Umleitung des Standardfehlers dient dazu, die Meldung "integer expression expected" zu verbergen, die die Bash ausgibt, wenn wir keine Zahl haben.

CAVEATS (dank der unten stehenden Kommentare):

  • Zahlen mit Dezimalpunkten sind no als gültige "Nummern" identifiziert
  • 使用方法 [[ ]] anstelle von [ ] wird immer ausgewertet zu true
  • Die meisten Nicht-Bash-Shells werten diesen Ausdruck immer als true
  • Das Verhalten in Bash ist undokumentiert und kann sich daher ohne Vorwarnung ändern
  • Wenn der Wert Leerzeichen nach der Zahl enthält (z. B. "1 a"), wird ein Fehler erzeugt, wie bash: [[: 1 a: syntax error in expression (error token is "a")
  • Wenn der Wert mit var-name identisch ist (z. B. i="i"), wird ein Fehler erzeugt, wie bash: [[: i: expression recursion level exceeded (error token is "i")

84voto

glenn jackman Punkte 221248

Niemand schlug Bash's erweiterter Mustervergleich :

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

oder mit eine POSIX-Zeichenklasse :

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

83voto

F. Hauri Punkte 57640

(2.) Vollständige Neuformulierung dieser Antwort: Jun 2021 27.

Einige Hinweise zur Leistung und Kompatibilität

Es gibt einige stark unterschiedliche Methoden für verschiedene Arten von Tests.

Ich habe die wichtigsten Methoden geprüft und diesen Vergleich erstellt.

Ganzzahl ohne Vorzeichen is_uint()

Diese Funktionen implementieren Code, um festzustellen, ob ein Ausdruck eine ganze Zahl ohne Vorzeichen ist, d. h. ausschließlich aus Ziffern besteht.

  • 使用方法 Parametererweiterung

    (Das war mein Ansatz vor all dem hier!)

    isuint_Parm() { [ "$1" ] && [ -z "${1//[0-9]}" ] ;}
  • 使用方法 Gabelung zu grep

    isuint_Grep() { grep -qE '^[0-9]+$' <<<"$1"; }

    Ich teste diese Methode nur einmal, weil sie sehr langsam ist. Dies ist nur dazu da, um zu zeigen, was man nicht tun sollte.

  • 使用方法 bash Ganzzahlfähigkeiten

    isuint_Bash() { (( 10#$1 >= 0 )) 2>/dev/null ;}
  • 使用方法 case

    isuint_Case() { case $1 in ''|*[!0-9]*) return 1;;esac;}
  • 使用方法 bash Der Regex

    isuint_Regx() { [[ $1 =~ ^[0-9]+$ ]] ;}

Ganzzahl mit Vorzeichen is_int()

Diese Funktionen implementieren Code, um festzustellen, ob ein Ausdruck eine ganze Zahl mit Vorzeichen ist, d. h. wie oben, aber mit einem optionalen Vorzeichen vor der Zahl.

  • 使用方法 Parametererweiterung

    isint_Parm() { local chk=${1#[+-]}; [ "$chk" ] && [ -z "${chk//[0-9]}" ] ;}
  • 使用方法 bash Ganzzahlfähigkeiten

    isint_Bash() { (( 10#$1 )) 2>/dev/null ;}
  • 使用方法 case

    isint_Case() { case ${1#[-+]} in ''|*[!0-9]*) return 1;;esac;}
  • 使用方法 bash Der Regex

    isint_Regx() { [[ $1 =~ ^[+-]?[0-9]+$ ]] ;}

Zahl (Float ohne Vorzeichen) is_num()

Diese Funktionen implementieren Code, um festzustellen, ob es sich bei einem Ausdruck um eine Fließkommazahl handelt, d.h. wie oben, aber mit einem optionalen Dezimalpunkt und zusätzlichen Nachkommastellen. Damit wird nicht versucht, numerische Ausdrücke in wissenschaftlicher Notation (z. B. 1.0234E-12) abzudecken.

  • 使用方法 Parametererweiterung

    isnum_Parm() { local ck=${1#[+-]};ck=${ck/.};[ "$ck" ]&&[ -z "${ck//[0-9]}" ];}
  • 使用方法 bash Der Regex

    isnum_Regx() { [[ $1 =~ ^[+-]?([0-9]+([.][0-9]*)?|\.[0-9]+)$ ]] ;}
  • 使用方法 case

    isnum_Case() { case ${1#[-+]} in ''|.|*[!0-9.]*|*.*.*) return 1;; esac ;}

Tests von Konzepten

(Sie könnten diesen Testcode nach den zuvor deklarierten Funktionen kopieren/einfügen).

testcases=(
    1 42 -3 +42 +3. .9 3.14 +3.141 -31.4 '' . 3-3 3.1.4 3a a3 blah 'Good day!'
);printf '%-12s %4s %4s %4s %4s %4s %4s %4s %4s %4s %4s %4s %4s\n' Function \
       U{Prm,Grp,Bsh,Cse,Rgx} I{Prm,Bsh,Cse,Rgx} N{Prm,Cse,Rgx}; \
for var in "${testcases[@]}";do
    outstr='';
    for func in isuint_{Parm,Grep,Bash,Case,Regx} isint_{Parm,Bash,Case,Regx} \
                       isnum_{Parm,Case,Regx};do
        if $func "$var"
        then outstr+='  num'
        else outstr+='  str'
        fi
    done
    printf '%-11s %s\n' "|$var|" "$outstr"
done

Sollte ausgegeben werden:

Function     UPrm UGrp UBsh UCse URgx IPrm IBsh ICse IRgx NPrm NCse NRgx
|1|           num  num  num  num  num  num  num  num  num  num  num  num
|42|          num  num  num  num  num  num  num  num  num  num  num  num
|-3|          str  str  str  str  str  num  num  num  num  num  num  num
|+42|         str  str  num  str  str  num  num  num  num  num  num  num
|+3.|         str  str  str  str  str  str  str  str  str  num  num  num
|.9|          str  str  str  str  str  str  str  str  str  num  num  num
|3.14|        str  str  str  str  str  str  str  str  str  num  num  num
|+3.141|      str  str  str  str  str  str  str  str  str  num  num  num
|-31.4|       str  str  str  str  str  str  str  str  str  num  num  num
||            str  str  num  str  str  str  str  str  str  str  str  str
|.|           str  str  str  str  str  str  str  str  str  str  str  str
|3-3|         str  str  num  str  str  str  str  str  str  str  str  str
|3.1.4|       str  str  str  str  str  str  str  str  str  str  str  str
|3a|          str  str  str  str  str  str  str  str  str  str  str  str
|a3|          str  str  str  str  str  str  str  str  str  str  str  str
|blah|        str  str  str  str  str  str  str  str  str  str  str  str
|Good day!|   str  str  str  str  str  str  str  str  str  str  str  str

Das hoffe ich! (Anmerkung: uint_bash scheinen nicht perfekt zu sein!)

Leistungsvergleich

Dann habe ich diese Testfunktion erstellt:

testFunc() {
    local tests=1000 start=${EPOCHREALTIME//.}
    for ((;tests--;)) ;do
        "$1" "$3"
    done
    printf -v "$2" %u $((${EPOCHREALTIME//.}-start))
}
percent(){ local p=00$((${1}00000/$2));printf -v "$3" %.2f%% ${p::-3}.${p: -3};}
sortedTests() {
    local func NaNTime NumTime ftyp="$1" nTest="$2" tTest="$3" min i pct line
    local -a order=()
    shift 3
    for func ;do
        testFunc "${ftyp}_$func" NaNTime "$tTest"
        testFunc "${ftyp}_$func" NumTime "$nTest"
        order[NaNTime+NumTime]=${ftyp}_$func\ $NumTime\ $NaNTime
    done
    printf '%-12s %11s %11s %14s\n' Function Number NaN Total
    min="${!order[*]}" min=${min%% *}
    for i in "${!order[@]}";do
        read -ra line <<<"${order[i]}"
        percent "$i" "$min" pct
        printf '%-12s %9d\U00B5s %9d\U00B5s  %12d\U00B5s  %9s\n' \
               "${line[@]}" "$i" "$pct"
    done
}

Ich könnte auf diese Weise laufen:

sortedTests isuint "This is not a number." 31415926535897932384 \
            Case Grep Parm Bash Regx ;\
sortedTests isint  "This is not a number." 31415926535897932384 \
            Case Parm Bash Regx ;\
sortedTests isnum "This string is clearly not a number..." \
            3.141592653589793238462643383279502884  Case Parm Regx

Auf meinem Rechner zeigt dies etwas wie:

Function             Number            NaN            Total        Rank
isuint_Case         8,080µs        6,848µs         14,928µs     100.00%
isuint_Parm        10,571µs       13,061µs         23,632µs     158.31%
isuint_Regx        12,865µs       15,407µs         28,272µs     189.39%
isuint_Bash        19,054µs       17,182µs         36,236µs     242.74%
isuint_Grep     1,333,786µs    1,416,626µs      2,750,412µs   18424.52%

Function             Number            NaN            Total        Rank
isint_Case          8,860µs        7,813µs         16,673µs     100.00%
isint_Parm         14,141µs       16,774µs         30,915µs     185.42%
isint_Regx         14,202µs       17,375µs         31,577µs     189.39%
isint_Bash         18,988µs       16,598µs         35,586µs     213.43%

Function             Number            NaN            Total        Rank
isnum_Case          8,935µs        9,232µs         18,167µs     100.00%
isnum_Parm         18,898µs       22,577µs         41,475µs     228.30%
isnum_Regx         25,336µs       42,825µs         68,161µs     375.19%

Sie können Folgendes herunterladen vollständiges Skript zum Vergleich von isnum hier o vollständiges isnum comparission script als Text hier. (mit UTF8- und LATIN-Behandlung).

Schlussfolgerung

  • case Weg ist eindeutig der schnellste! Etwa 3x schneller als regex und 2x schneller als mit Parametererweiterung .
  • Gabeln (an grep oder andere Binärdateien) sind zu vermeiden, wenn sie nicht benötigt werden.

case Methode ist meine bevorzugte Wahl geworden:

is_uint() { case $1        in '' | *[!0-9]*              ) return 1;; esac ;}
is_int()  { case ${1#[-+]} in '' | *[!0-9]*              ) return 1;; esac ;}
is_unum() { case $1        in '' | . | *[!0-9.]* | *.*.* ) return 1;; esac ;}
is_num()  { case ${1#[-+]} in '' | . | *[!0-9.]* | *.*.* ) return 1;; esac ;}

Über Kompatibilität

Hierfür habe ich ein kleines Testskript auf der Grundlage früherer Tests , mit:

for shell in bash dash 'busybox sh' ksh zsh "$@";do
    printf "%-12s  " "${shell%% *}"
    $shell < <(testScript) 2>&1 | xargs
done

Dies zeigt:

bash          Success
dash          Success
busybox       Success
ksh           Success
zsh           Success

Wie ich weiß, haben andere bash basierte Lösung wie regex y bash Die ganze Zahl wird in vielen anderen Shells nicht funktionieren und Gabeln sind ressourcenintensiv, ich würde die case Weg (kurz vor Parametererweiterung die ebenfalls weitgehend kompatibel ist).

CodeJaeger.com

CodeJaeger ist eine Gemeinschaft für Programmierer, die täglich Hilfe erhalten..
Wir haben viele Inhalte, und Sie können auch Ihre eigenen Fragen stellen oder die Fragen anderer Leute lösen.

Powered by:

X