(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).
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 dieexit 0
wird übersprungen, und der Code wird dann versuchenecho "bar"
. Scheitert sie auch daran, wird die&&
wird fehlschlagen, und es wird nicht einmal ausgeführtexit 1
! Mit aktuellenif
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 Siebash
um die hässlichsten (und sinnlosesten) Fehlermeldungen auszugeben...9 Stimmen
In Ubuntu funktioniert es nicht, es sei denn, Sie entfernen die Anführungszeichen nicht. Es sollte also einfach sein
[[ 12345 =~ ^[0-9]+$ ]] && echo OKKK || echo NOOO
0 Stimmen
...ich habe die (fehlerhafte) Antwort aus der Frage entfernt - Antworten sollten unter anderem deshalb getrennt sein, damit sie einzeln kommentiert, abgestimmt, korrigiert usw. werden können.
8 Stimmen
Sie müssen sein spezifischer was Sie meinen mit "Nummer" . Eine ganze Zahl? Eine Festkommazahl? Wissenschaftliche ("e") Notation? Ist ein bestimmter Bereich vorgeschrieben (z. B. ein 64-Bit-Wert ohne Vorzeichen), oder ist jede Zahl zulässig, die geschrieben werden kann?
0 Stimmen
Es ist unglaublich, dass die Bash keine zuverlässige, eingebaute Möglichkeit bietet, zu überprüfen, ob ein Wert numerisch ist oder nicht. Die Anzahl, die Vielfalt und die unterschiedliche Qualität der Antworten hier zeigen, dass dies ein ernstes Problem ist.
2 Stimmen
Die Bash bietet eine zuverlässige Methode, um festzustellen, ob eine Zahl ein INTEGER ist. { VAR="asdfas" ; (( VAR )) ; echo $?; } Die Gleichung wird korrekt fehlschlagen, wenn die Antwort "0" lautet, da "0" keine ganze Zahl ist. Ich hatte vor ein paar Minuten genau das gleiche Problem und habe diesen Thread mit einer schnellen Suche gefunden. Ich hoffe, das hilft anderen. Andere Leute waren allerdings nah dran.
0 Stimmen
@Der eine Typ aus dem Film Ihr Code gibt hässliche Meldungen auf stderr aus. Zum Beispiel
((5a))
gibtbash: ((: 5a: value too great for base (error token is "5a")