michahe
Anmeldungsdatum: 12. Dezember 2013
Beiträge: 784
|
Hallo, ich möchte einen String prüfen, ob er rein numerisch ist. Mein Code:
| #!/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
Projektleitung
Anmeldungsdatum: 16. Oktober 2011
Beiträge: 12439
|
Lass mal die Anführungsstriche um den Regex weg.
|
michahe
(Themenstarter)
Anmeldungsdatum: 12. Dezember 2013
Beiträge: 784
|
Danke rklm, so scheint es jetzt zu funktionieren:
#!/bin/bash
teststr="47"
if ! [[ "$teststr" =~ ^[0-9]+$ ]]; then
echo "$teststr: Not a number"
fi
|
kB
Supporter, Wikiteam
Anmeldungsdatum: 4. Oktober 2007
Beiträge: 7564
Wohnort: Münster
|
michahe schrieb: […] Was mache ich falsch?
Mehrere Fehler:
Die Anführungszeichen müssen weg. 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. 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. 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
Projektleitung
Anmeldungsdatum: 16. Oktober 2011
Beiträge: 12439
|
kB schrieb: michahe schrieb: […] Was mache ich falsch?
Mehrere Fehler:
Die Anführungszeichen müssen weg.
Check.
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: | $ 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 "==".
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?
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
Anmeldungsdatum: 20. Juli 2020
Beiträge: 582
|
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: 784
|
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
Supporter, Wikiteam
Anmeldungsdatum: 4. Oktober 2007
Beiträge: 7564
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
Supporter, Wikiteam
Anmeldungsdatum: 4. Oktober 2007
Beiträge: 7564
Wohnort: Münster
|
michahe schrieb: […]
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
$* liefert bessere Ergebnisse. Du kannst es selber ausprobieren.
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
Anmeldungsdatum: 20. Juli 2020
Beiträge: 582
|
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
Projektleitung
Anmeldungsdatum: 16. Oktober 2011
Beiträge: 12439
|
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: 784
|
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
Supporter, Wikiteam
Anmeldungsdatum: 4. Oktober 2007
Beiträge: 7564
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
Supporter, Wikiteam
Anmeldungsdatum: 4. Oktober 2007
Beiträge: 7564
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
Projektleitung
Anmeldungsdatum: 16. Oktober 2011
Beiträge: 12439
|
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.
|