Testen Sie, ob die Zeichenfolge eine gültige Ganzzahl ist

117

Ich versuche, etwas häufig genug zu tun: Benutzereingaben in einem Shell-Skript analysieren. Wenn der Benutzer eine gültige Ganzzahl angegeben hat, führt das Skript eine Sache aus, und wenn es nicht gültig ist, führt es etwas anderes aus. Das Problem ist, ich habe keinen einfachen (und einigermaßen eleganten) Weg gefunden, dies zu tun - ich möchte es nicht char für char auseinander nehmen müssen.

Ich weiß, dass das einfach sein muss, aber ich weiß nicht wie. Ich könnte es in einem Dutzend Sprachen machen, aber nicht BASH!

In meiner Forschung fand ich Folgendes:

Regulärer Ausdruck, um zu testen, ob eine Zeichenfolge aus einer gültigen reellen Zahl in Basis 10 besteht

Und darin gibt es eine Antwort, die über Regex spricht, aber soweit ich weiß, ist dies eine Funktion, die (unter anderem) in C verfügbar ist. Trotzdem hatte es eine großartige Antwort, also versuchte ich es mit grep, aber grep wusste nicht, was ich damit anfangen sollte. Ich habe -P ausprobiert, was auf meiner Box bedeutet, es als PERL-Regexp-Nada zu behandeln. Dash E (-E) hat auch nicht funktioniert. Und -F auch nicht -F.

Um ganz klar zu sein, ich versuche so etwas und suche nach einer Ausgabe - von dort aus werde ich das Skript hacken, um alles zu nutzen, was ich bekomme. (IOW, ich hatte erwartet, dass eine nicht konforme Eingabe nichts zurückgibt, während eine gültige Zeile wiederholt wird.)

snafu=$(echo "$2" | grep -E "/^[-+]?(?:\.[0-9]+|(?:0|[1-9][0-9]*)(?:\.[0-9]*)?)$/")
if [ -z "$snafu" ] ;
then
   echo "Not an integer - nothing back from the grep"
else
   echo "Integer."
fi

Würde jemand bitte veranschaulichen, wie dies am einfachsten gemacht werden kann?

Ehrlich gesagt ist dies meiner Meinung nach ein Mangel an TEST. Es sollte eine Flagge wie diese haben

if [ -I "string" ] ;
then
   echo "String is a valid integer."
else
   echo "String is not a valid integer."
fi
Richard T.
quelle
4
Zu Ihrer Information: [ist alt kompatibel test; [[ist Bashs neue Sache, mit mehr Operationen und anderen Angebotsregeln. Wenn Sie sich bereits entschieden haben, bei Bash zu bleiben, entscheiden Sie sich für [[(es ist wirklich viel schöner); Wenn Sie eine Portabilität auf andere Shells benötigen, vermeiden Sie dies [[vollständig.
Ephemient

Antworten:

183
[[ $var =~ ^-?[0-9]+$ ]]
  • Das ^zeigt den Beginn des Eingabemusters an
  • Das -ist ein wörtliches "-"
  • Das ?bedeutet "0 oder 1 des vorhergehenden ( -)"
  • Das +bedeutet "1 oder mehr der vorhergehenden ( [0-9])"
  • Das $zeigt das Ende des Eingabemusters an

Der reguläre Ausdruck entspricht also einer Option -(für den Fall negativer Zahlen), gefolgt von einer oder mehreren Dezimalstellen.

Referenzen :

Ignacio Vazquez-Abrams
quelle
3
Danke Ignacio, ich werde es gleich versuchen. Würde es Ihnen etwas ausmachen, es zu erklären, damit ich ein wenig lernen kann? Ich nehme an, es lautet: "Am Anfang der Zeichenfolge (^) ist ein Minuszeichen (-) optional (?), Gefolgt von einer beliebigen Anzahl von Zeichen zwischen null und einschließlich 9" ... und was könnte dann das + sein $ meine? Vielen Dank.
Richard T
10
Das +bedeutet "1 oder mehr des vorhergehenden" und das $gibt das Ende des Eingabemusters an. Der reguläre Ausdruck entspricht also einer Option, -gefolgt von einer oder mehreren Dezimalstellen.
Ignacio Vazquez-Abrams
murrt bezüglich: der ABS-Verbindung
Charles Duffy
Es ist eine Tangente, aber beachten Sie, dass Sie beim Festlegen von Zeichenbereichen ungerade Ergebnisse erzielen können. zum Beispiel, [A-z]würden Sie nicht nur geben A-Zund a-zaber auch \ , [, ], ^, _, und `.
Doktor J
Basierend auf der Zeichensortierung ( siehe diese verwandte Frage / Antwort ) könnte so etwas d[g-i]{2}nicht nur übereinstimmen, digsondern auch dishin der von dieser Antwort vorgeschlagenen Zusammenstellung (wobei der shDigraph als einzelnes Zeichen betrachtet wird, das danach sortiert wird h).
Doktor J
61

Wow ... hier gibt es so viele gute Lösungen !! Von allen oben genannten Lösungen stimme ich @nortally zu, dass die Verwendung des -eqeinen Liners am coolsten ist.

Ich verwende GNU Bash, Version 4.1.5(Debian). Ich habe dies auch auf ksh (SunSO 5.10) überprüft.

Hier ist meine Version der Überprüfung, ob $1es sich um eine Ganzzahl handelt oder nicht:

if [ "$1" -eq "$1" ] 2>/dev/null
then
    echo "$1 is an integer !!"
else
    echo "ERROR: first parameter must be an integer."
    echo $USAGE
    exit 1
fi

Dieser Ansatz berücksichtigt auch negative Zahlen, bei denen einige der anderen Lösungen ein fehlerhaftes negatives Ergebnis haben, und ermöglicht ein Präfix von "+" (z. B. +30), was offensichtlich eine ganze Zahl ist.

Ergebnisse:

$ int_check.sh 123
123 is an integer !!

$ int_check.sh 123+
ERROR: first parameter must be an integer.

$ int_check.sh -123
-123 is an integer !!

$ int_check.sh +30
+30 is an integer !!

$ int_check.sh -123c
ERROR: first parameter must be an integer.

$ int_check.sh 123c
ERROR: first parameter must be an integer.

$ int_check.sh c123
ERROR: first parameter must be an integer.

Die von Ignacio Vazquez-Abrams bereitgestellte Lösung war auch sehr ordentlich (wenn Sie Regex mögen), nachdem sie erklärt wurde. Es werden jedoch keine positiven Zahlen mit dem +Präfix verarbeitet, es kann jedoch wie folgt leicht behoben werden:

[[ $var =~ ^[-+]?[0-9]+$ ]]
Peter Ho
quelle
Nett! Ziemlich ähnlich wie diese , though.
devnull
Ja. Es ist ähnlich. Ich suchte jedoch nach einer Einzeilerlösung für die "if" -Anweisung. Ich dachte, dass ich dafür keine Funktion aufrufen muss. Außerdem kann ich sehen, dass die Umleitung des stderr zu stdout in der Funktion erfolgt. Als ich es versuchte, wurde die stderr-Meldung "Ganzzahliger Ausdruck erwartet" angezeigt, was für mich nicht wünschenswert war.
Peter Ho
Danke dir! Ich würde dies einfach und elegant nennen.
Ezra Nugroho
2
Es gibt einen bemerkenswerten Unterschied zwischen Ihrer Lösung und der Regex-Lösung: Die Größe der Ganzzahl wird auf Bash-Grenzwerte überprüft (auf meinem Computer sind es 64 Bit). Diese Grenze trifft die Regexp-Lösung nicht. Ihre Lösung schlägt also bei einer Nummer fehl, die auf 64-Bit-Computern streng größer als 9223372036854775807 ist.
Vaab
2
Wie ich kürzlich herausgefunden habe, gibt es einige Einschränkungen .
Kyle Strand
28

Nachzügler zur Party hier. Ich bin äußerst überrascht, dass in keiner der Antworten die einfachste, schnellste und tragbarste Lösung erwähnt wird. die caseAussage.

case ${variable#[-+]} in
  *[!0-9]* | '') echo Not a number ;;
  * ) echo Valid number ;;
esac

Das Trimmen eines Zeichens vor dem Vergleich fühlt sich wie ein Hack an, aber das macht den Ausdruck für die case-Aussage so viel einfacher.

Tripleee
quelle
4
Ich wünschte, ich könnte dies jedes Mal, wenn ich wegen Dupes auf diese Frage zurückkomme, einmal positiv bewerten. Es macht mich fertig, dass eine einfache, aber POSIX-konforme Lösung im Boden vergraben ist.
Adrian Frühwirth
3
Vielleicht sollten Sie sich um leere Saiten kümmern:''|*[!0-9]*)
Niklas Peter
2
Übrigens: Hier ist diese Syntax dokumentiert: tldp.org/LDP/abs/html/string-manipulation.html
Niklas Peter
Ich kann das ABS nicht besonders gutheißen. Dies ist natürlich auch im Bash-Handbuch dokumentiert. Wie auch immer, der Abschnitt, auf den Sie verlinkt haben, beschreibt nicht dieses spezielle Konstrukt, sondern zB die Antwort von @ Nortally.
Tripleee
@tripleee Das verknüpfte Dokument beschreibt das Konstrukt zum Entfernen eines Zeichenfolgenpräfix aus einer in der Fallzeile verwendeten Variablen. Es ist nur am Ende der Seite, aber es gibt keine Anker, so dass ich nicht direkt darauf verlinken konnte, siehe Abschnitt "Entfernen von Teilstrings"
Niklas Peter
10

Ich mag die Lösung mit dem -eqTest, weil es im Grunde ein Einzeiler ist.

Meine eigene Lösung bestand darin, die Parametererweiterung zu verwenden, um alle Ziffern wegzuwerfen und festzustellen, ob noch etwas übrig war. (Ich benutze immer noch 3.0, habe es noch nicht benutzt [[oder exprvorher, bin aber froh, sie kennenzulernen.)

if [ "${INPUT_STRING//[0-9]}" = "" ]; then
  # yes, natural number
else
  # no, has non-numeral chars
fi
nördlich
quelle
4
Dies kann mit einer [ -z "${INPUT_STRING//[0-9]}" ]wirklich schönen Lösung weiter verbessert werden !
ShellFish
Was ist mit negativen Vorzeichen?
Scottysseus
Die -eqLösung hat einige Probleme; siehe hier: stackoverflow.com/a/808740/1858225
Kyle Strand
Leer INPUT_STRING wird als Nummer betrachtet, schlägt also für meinen Fall fehl
Manwe
9

=~Verwenden Sie für die Portabilität auf Pre-Bash 3.1 (als der Test eingeführt wurde) expr.

if expr "$string" : '-\?[0-9]\+$' >/dev/null
then
  echo "String is a valid integer."
else
  echo "String is not a valid integer."
fi

expr STRING : REGEXSucht nach REGEX, das zu Beginn von STRING verankert ist, wobei die erste Gruppe (oder die Länge der Übereinstimmung, falls keine vorhanden) wiedergegeben wird und Erfolg / Misserfolg zurückgegeben wird. Dies ist die alte Regex-Syntax, daher der Überschuss \. -\?bedeutet "vielleicht -", [0-9]\+bedeutet "eine oder mehrere Ziffern" und $bedeutet "Ende der Zeichenfolge".

Bash unterstützt auch erweiterte Globs, obwohl ich mich nicht mehr daran erinnere, ab welcher Version.

shopt -s extglob
case "$string" of
    @(-|)[0-9]*([0-9]))
        echo "String is a valid integer." ;;
    *)
        echo "String is not a valid integer." ;;
esac

# equivalently, [[ $string = @(-|)[0-9]*([0-9])) ]]

@(-|)bedeutet " -oder nichts", [0-9]bedeutet "Ziffer" und *([0-9])bedeutet "null oder mehr Ziffern".

kurzlebig
quelle
Vielen Dank, ephemient, sehr verpflichtet. Ich hatte die = ~ -Syntax noch nie gesehen - und habe immer noch keine Ahnung, was sie bedeuten soll - ungefähr gleich?! ... Ich war noch nie begeistert, in BASH zu programmieren, aber es ist manchmal notwendig!
Richard T
In awk, ~war der "Regex" Operator. In Perl (wie von C kopiert) ~wurde bereits für "Bitkomplement" verwendet, also verwendeten sie =~. Diese spätere Notation wurde in mehrere andere Sprachen kopiert. (Perl 5.10 und Perl 6 mögen ~~mehr, aber das hat hier keine Auswirkungen.) Ich nehme an, Sie könnten es als eine Art ungefähre Gleichheit betrachten ...
Ephemient
Ausgezeichneter Beitrag UND bearbeiten! Ich schätze es sehr zu erklären, was es bedeutet. Ich wünschte, ich könnte sowohl Ihre als auch Ignacios Beiträge als DIE richtige Antwort markieren. -frown- Ihr seid beide großartig. Aber da Sie den doppelten Ruf haben, den er hat, gebe ich ihn Ignacio - hoffe, Sie verstehen! -smile-
Richard T
4

Hier ist noch eine andere Sichtweise (nur mit dem Befehl test builtin und seinem Rückkehrcode):

function is_int() { return $(test "$@" -eq "$@" > /dev/null 2>&1); } 

input="-123"

if $(is_int "${input}");
then
   echo "Input: ${input}"
   echo "Integer: $[${input}]"
else
   echo "Not an integer: ${input}"
fi
Hans
quelle
1
Es ist nicht notwendig, $()mit zu verwenden if. Das funktioniert : if is_int "$input". Auch das $[]Formular ist veraltet. Verwenden Sie $(())stattdessen. In beiden Fällen kann das Dollarzeichen weggelassen werden: echo "Integer: $((input))"Geschweifte Klammern sind an keiner Stelle in Ihrem Skript erforderlich.
Bis auf weiteres angehalten.
Ich hätte erwartet, dass dies auch Zahlen in Bashs Basisnotation als gültige Ganzzahlen behandelt (was natürlich per Definition ist; aber es stimmt möglicherweise nicht mit Ihrer überein), testscheint dies jedoch nicht zu unterstützen. [[tut es aber. [[ 16#aa -eq 16#aa ]] && echo integerdruckt "Ganzzahl".
Tripleee
Beachten Sie, dass [[für diese Methode falsch positive Ergebnisse zurückgegeben werden. zB [[ f -eq f ]]gelingt. Also muss es testoder verwenden [.
Spinup
3

Sie können Nicht-Ziffern entfernen und einen Vergleich durchführen. Hier ist ein Demo-Skript:

for num in "44" "-44" "44-" "4-4" "a4" "4a" ".4" "4.4" "-4.4" "09"
do
    match=${num//[^[:digit:]]}    # strip non-digits
    match=${match#0*}             # strip leading zeros
    echo -en "$num\t$match\t"
    case $num in
        $match|-$match)    echo "Integer";;
                     *)    echo "Not integer";;
    esac
done

So sieht die Testausgabe aus:

44 44 Ganzzahl
-44 44 Ganzzahl
44- 44 Nicht ganzzahlig
4-4 44 Nicht ganzzahlig
a4 4 Nicht ganzzahlig
4a 4 Nicht ganzzahlig
.4 4 Nicht ganzzahlig
4.4 44 Nicht ganzzahlig
-4.4 44 Nicht ganzzahlig
09 9 Nicht ganzzahlig
Bis auf weiteres angehalten.
quelle
Hallo Dennis, danke, dass du mir die Syntax rechts von match = oben vorgestellt hast. Ich habe diese Typensyntax noch nie bemerkt. Ich erkenne einen Teil der Syntax von tr (ein Dienstprogramm, das ich nicht ganz beherrsche, aber manchmal durchfummle); Wo kann ich mich über eine solche Syntax informieren? (dh wie heißt so etwas?) Danke.
Richard T
Sie können in der Bash-Manpage im Abschnitt "Parametererweiterung" nach Informationen über ${var//string}und ${var#string}im Abschnitt "Pattern Matching" für [^ [: digit:]] `suchen (der ebenfalls behandelt wird man 7 regex).
Bis auf weiteres angehalten.
1
match=${match#0*}nicht nicht führenden Nullen Streifen, Streifen es höchstens eine Null. Mit Expansion kann dies nur mit extglobvia erreicht werden match=${match##+(0)}.
Adrian Frühwirth
Ist 9 oder 09 nicht eine ganze Zahl?
Mike Q
@MikeQ: 09ist keine Ganzzahl, wenn Sie davon ausgehen, dass eine Ganzzahl keine führenden Nullen enthält. Der Test ist, ob die Eingabe ( 09) einer bereinigten Version ( 9- eine Ganzzahl) entspricht und nicht.
Bis auf weiteres angehalten.
2

Für mich war die einfachste Lösung, die Variable in einem (())Ausdruck wie folgt zu verwenden:

if ((VAR > 0))
then
  echo "$VAR is a positive integer."
fi

Diese Lösung ist natürlich nur gültig, wenn ein Wert von Null für Ihre Anwendung keinen Sinn ergibt. Das stimmte in meinem Fall, und das ist viel einfacher als die anderen Lösungen.

Wie in den Kommentaren erwähnt, kann dies zu einem Angriff auf die Codeausführung führen: Der (( ))Operator wertet aus VAR, wie im Arithmetic EvaluationAbschnitt der Manpage bash (1) angegeben . Daher sollten Sie diese Technik nicht verwenden, wenn die Quelle des Inhalts von VARungewiss ist (und natürlich auch KEINE andere Form der variablen Erweiterung verwenden).

Trebor unhöflich
quelle
Sie können sogar einfacher mitif (( var )); then echo "$var is an int."; fi
Aaron R.
2
Dies gilt jedoch auch für negative Ganzzahlen, @aaronr, nicht für das, wonach das OP gesucht hat.
Trebor Rude
2
Dies ist gefährlich, siehe: n = 1; var = "n"; if ((var)); dann echo "$ var ist ein int."; fi
jarno
2
Dies ist eine sehr schlechte Idee und unterliegt der Ausführung von willkürlichem Code: Probieren Sie es selbst aus : VAR='a[$(ls)]'; if ((VAR > 0)); then echo "$VAR is a positive integer"; fi. An diesem Punkt bist du froh, dass ich stattdessen keinen bösen Befehl eingegeben habe ls. Da OP Benutzereingaben erwähnt , hoffe ich wirklich, dass Sie diese nicht mit Benutzereingaben im Produktionscode verwenden!
gniourf_gniourf
Dies funktioniert nicht, wenn die Zeichenfolge einige Ziffern enthält wie:agent007
brablc
1

oder mit sed:

   test -z $(echo "2000" | sed s/[0-9]//g) && echo "integer" || echo "no integer"
   # integer

   test -z $(echo "ab12" | sed s/[0-9]//g) && echo "integer" || echo "no integer"
   # no integer
Knipwim
quelle
In Bash und einigen anderen "Bourne plus" -Shells können Sie die Befehlsersetzung und den externen Befehl mit test -z "${string//[0-9]/}" && echo "integer" || echo "no integer"... vermeiden, obwohl dies im Grunde Dennis Williamsons Antwort
Tripleee
Vielen Dank! Die einzige Antwort, die hier tatsächlich funktioniert!
Benutzer
Stille Alternative:if [[ -n "$(printf "%s" "${2}" | sed s/[0-9]//g)" ]]; then
Benutzer
0

Hinzufügen zur Antwort von Ignacio Vazquez-Abrams. Dadurch kann das + -Zeichen vor der Ganzzahl stehen, und es können beliebig viele Nullen als Dezimalstellen verwendet werden. Auf diese Weise kann beispielsweise +45.00000000 als Ganzzahl betrachtet werden.
$ 1 muss jedoch so formatiert sein, dass es einen Dezimalpunkt enthält. 45 wird hier nicht als Ganzzahl betrachtet, 45.0 jedoch.

if [[ $1 =~ ^-?[0-9]+.?[0]+$ ]]; then
    echo "yes, this is an integer"
elif [[ $1 =~ ^\+?[0-9]+.?[0]+$ ]]; then
    echo "yes, this is an integer"
else
    echo "no, this is not an integer"
fi
JustinMT
quelle
Gibt es einen Grund, warum Sie zwei verschiedene reguläre Ausdrücke für positive und negative Zahlen anstelle von ^[-+]?[0-9]... verwenden?
Tripleee
0

Zum Lachen habe ich ungefähr schnell eine Reihe von Funktionen ausgearbeitet, um dies zu tun (is_string, is_int, is_float, is alpha string oder andere), aber es gibt effizientere (weniger Code) Möglichkeiten, dies zu tun:

#!/bin/bash

function strindex() {
    x="${1%%$2*}"
    if [[ "$x" = "$1" ]] ;then
        true
    else
        if [ "${#x}" -gt 0 ] ;then
            false
        else
            true
        fi
    fi
}

function is_int() {
    if is_empty "${1}" ;then
        false
        return
    fi
    tmp=$(echo "${1}" | sed 's/[^0-9]*//g')
    if [[ $tmp == "${1}" ]] || [[ "-${tmp}" == "${1}" ]] ; then
        #echo "INT (${1}) tmp=$tmp"
        true
    else
        #echo "NOT INT (${1}) tmp=$tmp"
        false
    fi
}

function is_float() {
    if is_empty "${1}" ;then
        false
        return
    fi
    if ! strindex "${1}" "-" ; then
        false
        return
    fi
    tmp=$(echo "${1}" | sed 's/[^a-z. ]*//g')
    if [[ $tmp =~ "." ]] ; then
        #echo "FLOAT  (${1}) tmp=$tmp"
        true
    else
        #echo "NOT FLOAT  (${1}) tmp=$tmp"
        false
    fi
}

function is_strict_string() {
    if is_empty "${1}" ;then
        false
        return
    fi
    if [[ "${1}" =~ ^[A-Za-z]+$ ]]; then
        #echo "STRICT STRING (${1})"
        true
    else
        #echo "NOT STRICT STRING (${1})"
        false
    fi
}

function is_string() {
    if is_empty "${1}" || is_int "${1}" || is_float "${1}" || is_strict_string "${1}" ;then
        false
        return
    fi
    if [ ! -z "${1}" ] ;then
        true
        return
    fi
    false
}
function is_empty() {
    if [ -z "${1// }" ] ;then
        true
    else
        false
    fi
}

Führen Sie hier einige Tests durch, ich habe definiert, dass -44 ein int ist, aber 44- nicht etc ..:

for num in "44" "-44" "44-" "4-4" "a4" "4a" ".4" "4.4" "-4.4" "09" "hello" "h3llo!" "!!" " " "" ; do
    if is_int "$num" ;then
        echo "INT = $num"

    elif is_float "$num" ;then
        echo "FLOAT = $num"

    elif is_string "$num" ; then
        echo "STRING = $num"

    elif is_strict_string "$num" ; then
        echo "STRICT STRING = $num"
    else
        echo "OTHER = $num"
    fi
done

Ausgabe:

INT = 44
INT = -44
STRING = 44-
STRING = 4-4
STRING = a4
STRING = 4a
FLOAT = .4
FLOAT = 4.4
FLOAT = -4.4
INT = 09
STRICT STRING = hello
STRING = h3llo!
STRING = !!
OTHER =  
OTHER = 

HINWEIS: Führende Nullen können beim Hinzufügen von Zahlen wie Oktal auf etwas anderes schließen. Daher ist es besser, sie zu entfernen, wenn Sie beabsichtigen, '09' als int zu behandeln (was ich tue) (z. B. expr 09 + 0oder mit sed entfernen).

Mike Q.
quelle