ubuntuusers.de

BASH: RegExp isNumeric

Status: Ungelöst | Ubuntu-Version: Kubuntu 20.04 (Focal Fossa)
Antworten |

michahe

Anmeldungsdatum:
12. Dezember 2013

Beiträge: 857

Hallo,

ich möchte einen String prüfen, ob er rein numerisch ist. Mein Code:

1
2
3
4
5
#!/bin/bash
 teststr="47"
if ! [[ "$teststr" =~ '^[0-9]+$' ]]; then
   echo "$teststr: Not a number"
fi

liefert:

47: Not a number

Was mache ich falsch? Danke, Michael

Bearbeitet von rklm:

Syntaxhighlighting

rklm Team-Icon

Projektleitung

Anmeldungsdatum:
16. Oktober 2011

Beiträge: 13174

Lass mal die Anführungsstriche um den Regex weg.

michahe

(Themenstarter)

Anmeldungsdatum:
12. Dezember 2013

Beiträge: 857

Danke rklm, so scheint es jetzt zu funktionieren:

#!/bin/bash
 teststr="47"
if ! [[ "$teststr" =~ ^[0-9]+$ ]]; then
   echo "$teststr: Not a number"
fi

kB Team-Icon

Supporter, Wikiteam
Avatar von kB

Anmeldungsdatum:
4. Oktober 2007

Beiträge: 9626

Wohnort: Münster

michahe schrieb:

[…] Was mache ich falsch?

Mehrere Fehler:

  1. Die Anführungszeichen müssen weg.

  2. Bash verwendet für reguläre Ausdrücke eine andere Schreibweise. Statt "[0-9]+" für „eine oder mehrere Ziffern“ muss man bei der Bash "+([0-9])" schreiben.

  3. Du willst ja nicht prüfen, ob der Teststring eine Zahl enthält, sondern ob er eine Zahl ist. Das geht mit einem anderen Operator.

  4. Da man in der Regel den Test mehrfach im Skript benötigt, schreibt man sich eine Funktion.

Ich verwende z.B. so etwas:

#!/bin/bash
uint ()		[[ $* == +([0-9]) ]]

teststr=42
uint $teststr || echo "$teststr: Not a number"

rklm Team-Icon

Projektleitung

Anmeldungsdatum:
16. Oktober 2011

Beiträge: 13174

kB schrieb:

michahe schrieb:

[…] Was mache ich falsch?

Mehrere Fehler:

  1. Die Anführungszeichen müssen weg.

Check.

  1. Bash verwendet für reguläre Ausdrücke eine andere Schreibweise. Statt "[0-9]+" für „eine oder mehrere Ziffern“ muss man bei der Bash "+([0-9])" schreiben.

Das ist beim Globbing der Fall. Aber hier funktioniert es einwandfrei:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
$ for i in '' a 1b 3 456; do echo "<$i>"; [[ "$i" =~ ^[0-9]+$ ]]; echo ">> $?"; done
<>
>> 1
<a>
>> 1
<1b>
>> 1
<3>
>> 0
<456>
>> 0

Die Syntax, die Du empfiehlst, ist die für Pathname Expansion vulgo: Globbing. Erstaunlich, dass die hier auch funktioniert - zusammen mit "==".

  1. Du willst ja nicht prüfen, ob der Teststring eine Zahl enthält, sondern ob er eine Zahl ist. Das geht mit einem anderen Operator.

Welcher?

  1. Da man in der Regel den Test mehrfach im Skript benötigt, schreibt man sich eine Funktion.

Sinnvoll, wenn es mehrfach gebraucht wird.

Ich verwende z.B. so etwas:

#!/bin/bash
uint ()		[[ $* == +([0-9]) ]]

teststr=42
uint $teststr || echo "$teststr: Not a number"

Da würde ich aber $1 statt $* verwenden, denn dann gibt es kein Problem mit überzähligen Argumenten.

In einer normalen sh geht auch expr

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
$ for i in '' a 1b 3 456 c7; do echo "<$i>"; expr "$i" : '[0-9]\+$' >/dev/null; echo ">> $?"; done
<>
>> 1
<a>
>> 1
<1b>
>> 1
<3>
>> 0
<456>
>> 0
<c7>
>> 1

shiro Team-Icon

Supporter

Anmeldungsdatum:
20. Juli 2020

Beiträge: 1246

kB schrieb:

2. Bash verwendet für reguläre Ausdrücke eine andere Schreibweise. Statt "[0-9]+" für „eine oder mehrere Ziffern“ muss man bei der Bash "+([0-9])" schreiben.

Diese Aussage ist mir neu. Wo ist das so definiert? Ich kenne nur die "übliche" Form, also "[0-9]+" oder "^[0-9]{2}$" (zweistellige Zahl).

Es sei denn man will mit "()" gruppieren und auf das Quoting verzichten. Dann macht "*()" oder "+()" Sinn. In obigem Beispiel ist diese Notwendigkeit für mich allerdings nicht erkennbar.

michahe

(Themenstarter)

Anmeldungsdatum:
12. Dezember 2013

Beiträge: 857

Danke, aber jetzt bin ich vollkommen verwirrt. Ich hätte nichts gegen die Funktion, aber

  • sie sollte in beiden Fällen ein Ergebnis liefern, z.B.

    $ uint "22 33"
    keine Zahl
  • welche Syntax: $* ODER $1

  • was bedeuten die Leerzeichen

    uint ()		[[ $* == +([0-9]) ]]
  • ShellCheck.net sagt:

    > uint ()                [[ $* == +([0-9]) ]]
    > ^-- SC1073 (error): Couldn't parse this function. Fix to allow more checks.
    >                 ^-- SC1064 (error): Expected a { to open the function definition.
    >                 ^-- SC1072 (error): Fix any mentioned problems and try again.

Mein (unvollständiger) Code zum Testen:

#!/bin/bash
uint ()		[[ $* == +([0-9]) ]]   # $1 ODER $1? und Rückgabewerte erzeugen und Schreibweise (ShellCheck.net)
do while true
	input teststr
	uint $teststr
done

kB Team-Icon

Supporter, Wikiteam
Avatar von kB

Anmeldungsdatum:
4. Oktober 2007

Beiträge: 9626

Wohnort: Münster

shiro schrieb:

[…] Wo ist das so definiert?

Suche mal im Manual der Bash.

[…] man will […] auf das Quoting verzichten

Was man in diesem Fall sogar muss, weil innerhalb von [[ … ]] das Quoting der rechten Seite die Arbeitsweise des Vergleichsoperators verändert.

kB Team-Icon

Supporter, Wikiteam
Avatar von kB

Anmeldungsdatum:
4. Oktober 2007

Beiträge: 9626

Wohnort: Münster

michahe schrieb:

[…]

  • sie sollte in beiden Fällen ein Ergebnis liefern […]

Ich nehme an, Du meinst, sie soll für jede Eingabe ein Ergebnis zurückmelden. Das tut sie:

  • Sie meldet für Zeichenfolgen, die nur Ziffern enthalten, den Wert 0 für wahr zurück und

  • bei leeren und allen Zeichenfolgen, welche mindestens ein anderes Zeichen (inkl. Leerzeichen) enthalten, einen Wert ungleich 0 für falsch zurück.

Freilich muss man den normalerweise unsichtbaren Rückgabewert auch auswerten, z.B.

$ for X in '' a 42 '4 2' a5 7b ; do uint $X ; echo «$X», $? ; done
«», 1
«a», 1
«42», 0
«4 2», 1
«a5», 1
«7b», 1
  • welche Syntax: $* ODER $1

$* liefert bessere Ergebnisse. Du kannst es selber ausprobieren.

  • was bedeuten die Leerzeichen

Mit Whitespace trennt man Syntaxelemente voneinander ab, damit der Parser sie erkennen kann. In diesem Fall den Namen des Symbols (= uint()), idF. ein Funktionsname, von dessen Wert, idF. der Funktionsrumpf bzw. der von der Funktion beim Aufruf auszuführende Code.

shiro Team-Icon

Supporter

Anmeldungsdatum:
20. Juli 2020

Beiträge: 1246

kB schrieb:

[…] Wo ist das so definiert?

Suche mal im Manual der Bash.

Hmm, ich habe mich wohl nicht präzise genug ausgedrückt. Mir ist klar, dass seit Bash 3.0 bei "[[ ... ]]" der Operator "=~" die rechte Seite als erweiterten regulären POSIX 1003.2 Ausdruck interpretiert (so wie egrep).

Auf den von dir geklammerte Part "([0-9])" kann dann über "${BASH_REMATCH[1]}" zugegriffen werden. Mich hat das führende "+" vor der Gruppierung irritiert, das aber von der Bash auch nicht angemeckert wird.

Normalerweise beziehen sich die repeating Indicatoren (+,*) nach einem regex-Objekt auf die zuvorige Definition. Da vor dem "+" allerdings ein "leeres" Objekt steht, hätte ich erwartet, dass diese Funktion ignoriert wird; praktisch als lexigrafisches Element, damit die nicht escapede Klammer "(" keinen Syntax-Error wirft. Tatsächlich wird der repeating Indicator aber auf das folgende geklammerte Gruppen-Element angewendet, so als wäre es nach der Klammerung gesetzt. Dieses Verhalten war mir neu. Ich habe hierzu leider auch in den einschlägigen Beschreibungen (dazu gehört natürlich auch das GNU "Bash Reference Manual") nichts gefunden.

Aus diesem Grund hatte ich gefragt, wo du diese (ja funktionierende) Definition gefunden hast. Ich bin nicht fündig geworden weshalb ich mit diesem Post noch mal frage. Sollte diese Frage zur Klärung des Posts von michahe als "hijacking" verstanden werden, bitte ich um Verschiebung des Posts.

rklm Team-Icon

Projektleitung

Anmeldungsdatum:
16. Oktober 2011

Beiträge: 13174

shiro schrieb:

Normalerweise beziehen sich die repeating Indicatoren (+,*) nach einem regex-Objekt auf die zuvorige Definition. Da vor dem "+" allerdings ein "leeres" Objekt steht, hätte ich erwartet, dass diese Funktion ignoriert wird; praktisch als lexigrafisches Element, damit die nicht escapede Klammer "(" keinen Syntax-Error wirft. Tatsächlich wird der repeating Indicator aber auf das folgende geklammerte Gruppen-Element angewendet, so als wäre es nach der Klammerung gesetzt. Dieses Verhalten war mir neu. Ich habe hierzu leider auch in den einschlägigen Beschreibungen (dazu gehört natürlich auch das GNU "Bash Reference Manual") nichts gefunden.

https://www.gnu.org/savannah-checkouts/gnu/bash/manual/bash.html#Pattern-Matching

Was ich bisher nicht gefunden habe: wo geschrieben steht, dass innerhalb [[ ... ]] ein Pattern Matching mit == gemacht werden kann, wie von kB beschrieben.

michahe

(Themenstarter)

Anmeldungsdatum:
12. Dezember 2013

Beiträge: 857

Danke kB, aber schade, von Deinen Ausführungen habe ich nichts verstanden.

Ich habe mich "durchgewurschtelt", so funktioniert mein Code jetzt und ShellCheck.net ist auch zufrieden:

#!/bin/bash
uint () {
	[[ $* == +([0-9]) ]]
}
while true; do
	read -r teststr                         # String einlesen
	uint "$teststr"                         # Funktion aufrufen
	PruefErgebnis=$?                        # Prüfergebnis retten, Quelle: https://it-muecke.de/node:679
	echo »»»"$teststr"«««, $PruefErgebnis
	if [ $PruefErgebnis == 1 ]; then
		echo "keine Zahl"
	else
		echo "OK, Zahl"
fi
done

@rklm:

#!/bin/bash
uint ()		[[ $* == +([0-9]) ]]
teststr=42
uint $teststr || echo "$teststr: Not a number"

Da würde ich aber $1 statt $* verwenden, denn dann gibt es kein Problem mit überzähligen Argumenten.

$1 funktioniert nicht (siehe mein aktueller Codeblock): Nach Austausch $* → $1 wird für "4 2" eine Zahl gemeldet, ist es aber nicht.

kB Team-Icon

Supporter, Wikiteam
Avatar von kB

Anmeldungsdatum:
4. Oktober 2007

Beiträge: 9626

Wohnort: Münster

michahe schrieb:

[…] so funktioniert mein Code jetzt und ShellCheck.net ist auch zufrieden

Es ist völlig unklar, was diese Internet-Seite eigentlich tun soll und vor allen Dingen, was sie denn als normale und zulässige Formulierungen akzeptiert und jedenfalls nicht mit irgendwelchen Meldungen anmeckert. Natürlich unterscheiden sich die verschiedenen Shells und sogar innerhalb einer Shell deren verschiedene Versionen, was jeweils zulässig ist. Man sollte daher die Ergebnisse nicht überbewerten. Dir geht es ja speziell um die Bash und nicht um eine ideale Shell nach den Vorstellungen der Programmierer dieser Internet-Seite.

Es spricht syntaktisch nichts gegen Dein Programm:

#!/bin/bash
uint () {
	[[ $* == +([0-9]) ]]
}
while true; do
	read -r teststr                         # String einlesen
	uint "$teststr"                         # Funktion aufrufen
	PruefErgebnis=$?                        # Prüfergebnis retten, Quelle: https://it-muecke.de/node:679
	echo »»»"$teststr"«««, $PruefErgebnis
	if [ $PruefErgebnis == 1 ]; then
		echo "keine Zahl"
	else
		echo "OK, Zahl"
fi
done

Es ist lediglich an vielen Stellen unnötig umständlich. Dieses Programm leistet dasselbe, muss aber nicht per Strg+C abgebrochen werden:

#!/bin/bash
uint () [[ $* == +([0-9]) ]]

while read -r teststr                         # String einlesen
do 	if uint $teststr                         # Funktion aufrufen zur Prüfung
        then echo OK, Zahl
        else echo keine Zahl
        fi
done

Die Wirkungsweise ist ganz einfach: uint gibt true oder false zurück. Wenn der Ausdruck nach if sich auf true reduzieren lässt, wird der then-Zweig ausgeführt, sonst der else-Zweig. Die while-Schleife wird solange ausgeführt, wie die Funktion read etwas lesen kann.

Es ginge sogar noch kürzer, aber versuche erst einmal dieses Beispiel zu verstehen.

kB Team-Icon

Supporter, Wikiteam
Avatar von kB

Anmeldungsdatum:
4. Oktober 2007

Beiträge: 9626

Wohnort: Münster

rklm schrieb:

[…] Was ich bisher nicht gefunden habe: wo geschrieben steht, dass innerhalb [[ ... ]] ein Pattern Matching mit == gemacht werden kann

Es steht in der Manpage von Bash, wie bereits erwähnt. Allerdings ist – wie gerade beim Durchsuchen der lokalen Kopie dieser Manpage bemerkt – jedenfalls bei mir diese Manpage verstümmelt und der Teil zum Pattern Matching fehlt. Ist das bei Euch auch so?

Die vollständige Seite lässt sich hier durchsuchen:
https://linux.die.net/man/1/bash

Man suche zuerst nach „[[ expression ]]“ und findet dann Erläuterungen zu den Operatoren ==, != und =~.

Danach suche man nach „Pattern Matching“. Die 5. von 7 Stellen ist die Überschrift der Erläuterungen. Die gesuchte Form "+(…)" ist einer der "extended pattern matching operators".

rklm Team-Icon

Projektleitung

Anmeldungsdatum:
16. Oktober 2011

Beiträge: 13174

kB schrieb:

rklm schrieb:

[…] Was ich bisher nicht gefunden habe: wo geschrieben steht, dass innerhalb [[ ... ]] ein Pattern Matching mit == gemacht werden kann

Es steht in der Manpage von Bash, wie bereits erwähnt. Allerdings ist – wie gerade beim Durchsuchen der lokalen Kopie dieser Manpage bemerkt – jedenfalls bei mir diese Manpage verstümmelt und der Teil zum Pattern Matching fehlt. Ist das bei Euch auch so?

Meine ist OK.

Die vollständige Seite lässt sich hier durchsuchen:

Ah ja, Compound Commands. Danke! Ich benutze ja praktisch immer die sh fürs Skripten und deshalb natürlich auch nicht [[ ... ]]. Sollte ich mir mal in Ruhe genauer anschauen.

Antworten |