Überprüfen Sie, ob eine Zeichenfolge mit einem regulären Ausdruck im Bash-Skript übereinstimmt

204

Eines der Argumente, die mein Skript erhält, ist ein Datum im folgenden Format : yyyymmdd.

Ich möchte überprüfen, ob ich ein gültiges Datum als Eingabe erhalte.

Wie kann ich das machen? Ich versuche einen regulären Ausdruck wie:[0-9]\{\8}

Peter Nijem
quelle
Es ist einfach zu überprüfen, ob das Format stimmt. Aber ich glaube nicht, dass Sie in Bash (mit eingebauten) überprüfen können, ob das Datum gültig ist.
RedX

Antworten:

315

Sie können das Testkonstrukt [[ ]]zusammen mit dem Übereinstimmungsoperator für reguläre Ausdrücke verwenden =~, um zu überprüfen, ob eine Zeichenfolge mit einem Regex-Muster übereinstimmt.

Für Ihren speziellen Fall können Sie schreiben:

[[ $date =~ ^[0-9]{8}$ ]] && echo "yes"

Oder genauer gesagt ein genauer Test:

[[ $date =~ ^[0-9]{4}(0[1-9]|1[0-2])(0[1-9]|[1-2][0-9]|3[0-1])$ ]] && echo "yes"
#           |^^^^^^^^ ^^^^^^ ^^^^^^  ^^^^^^ ^^^^^^^^^^ ^^^^^^ |
#           |   |     ^^^^^^^^^^^^^  ^^^^^^^^^^^^^^^^^^^^^^^^ |
#           |   |          |                   |              |
#           |   |           \                  |              |
#           | --year--   --month--           --day--          |
#           |          either 01...09      either 01..09     end of line
# start of line            or 10,11,12         or 10..29
#                                              or 30, 31

Das heißt, Sie können in Bash einen regulären Ausdruck definieren, der dem gewünschten Format entspricht. So können Sie vorgehen:

[[ $date =~ ^regex$ ]] && echo "matched" || echo "did not match"

Dabei werden Befehle nach &&ausgeführt, wenn der Test erfolgreich ist, und Befehle nach ||ausgeführt, wenn der Test nicht erfolgreich ist.

Beachten Sie, dass dies auf der Lösung von Aleks-Daniel Jakimenko in der Überprüfung des Datumsformats für Benutzereingaben in Bash basiert .


In anderen Shells können Sie grep verwenden . Wenn Ihre Shell POSIX-kompatibel ist, tun Sie dies

(echo "$date" | grep -Eq  ^regex$) && echo "matched" || echo "did not match"

Bei Fischen , die nicht POSIX-konform sind, können Sie dies tun

echo "$date" | grep -Eq "^regex\$"; and echo "matched"; or echo "did not match"
fedorqui 'SO hör auf zu schaden'
quelle
19
Ich bin mir dessen bewusst, aber ich berücksichtige auch gerne, wer fragt und wie weit sie mit Bash sind. Wenn wir sehr komplexe Bedingungen bieten, werden sie nichts lernen und einfach zurückkommen, wenn sie einen anderen Zweifel haben. Ich gebe lieber eine verständlichere Antwort.
Fedorqui 'SO hör auf,'
7
Heh. Der einzige Weg zu lernen ist, viel guten Code zu lesen. Wenn Sie falschen Code angeben, der leicht zu verstehen ist, dessen Verwendung jedoch nicht empfohlen wird, ist dies eine schlechte Art zu unterrichten. Ich bin mir auch ziemlich sicher, dass diejenigen, die gerade erst angefangen haben, Bash zu lernen (wahrscheinlich bereits einige Teile einer anderen Sprache kennen), die Bash-Syntax für Regex leichter verstehen werden als einige grepBefehle mit -EFlag.
Aleks-Daniel Jakimenko-A.
8
@ Aleks-DanielJakimenko Ich habe diesen Beitrag noch einmal durchgesehen und jetzt stimme ich zu, dass es am besten ist, Bash Regex zu verwenden. Vielen Dank für den Hinweis in die gute Richtung, aktualisierte Antwort.
Fedorqui 'SO hör auf zu schaden'
4
Upvote, das erlaubt es, es ein wenig jenseits der OP-Frage zu verwenden, zum Beispiel für sh ..
Dereckson
3
@ Aleks-DanielJakimenko grep scheint die beste Option zu sein , wenn Sie verwenden sh, fishoder andere weniger gut ausgestattete Muscheln.
Tomékwi
47

In Bash Version 3 können Sie den Operator '= ~' verwenden:

if [[ "$date" =~ ^[0-9]{8}$ ]]; then
    echo "Valid date"
else
    echo "Invalid date"
fi

Referenz: http://tldp.org/LDP/abs/html/bashver3.html#REGEXMATCHREF

HINWEIS: Das Anführungszeichen im Matching-Operator in den doppelten Klammern [[]] ist ab Bash Version 3.2 nicht mehr erforderlich

aliasav
quelle
20
Sie sollten char nicht bei regulären Ausdrücken verwenden? Denn wenn ich Ausdrücke verwende, funktioniert das nicht
Dawid Drozd
Darüber hinaus ist das Backslash-Entweichen von {und} ähnlich problematisch.
kbulgrien
32

Eine gute Möglichkeit, um zu testen, ob eine Zeichenfolge ein korrektes Datum ist, besteht darin, das Befehlsdatum zu verwenden:

if date -d "${DATE}" >/dev/null 2>&1
then
  # do what you need to do with your date
else
  echo "${DATE} incorrect date" >&2
  exit 1
fi

Aus Kommentar: Man kann die Formatierung verwenden

if [ "2017-01-14" == $(date -d "2017-01-14" '+%Y-%m-%d') ] 
Django Janny
quelle
9
Bewerten Sie Ihre Antwort hoch, da die Datumsfunktion die Datumsangaben und nicht die fehleranfälligen regulären Ausdrücke verarbeiten kann
Ali
Dies ist gut, um allgemeine Datumsoptionen zu überprüfen. Wenn Sie jedoch ein bestimmtes Datumsformat überprüfen müssen, kann dies durchgeführt werden? Wenn ich date -d 2017-11-14ees zum Beispiel mache, wird es am 14. November um 05:00:00 UTC 2017 zurückgegeben, aber das würde mein Skript beschädigen.
Josiah
1
Sie könnten so etwas verwenden: if ["2017-01-14" == $ (Datum -d "2017-01-14" '+% Y-% m-% d')] Es wird geprüft, ob das Datum korrekt ist und prüfen Sie, ob das Ergebnis mit Ihren eingegebenen Daten übereinstimmt. Übrigens, seien Sie sehr vorsichtig mit dem lokalisierten Datumsformat (zum Beispiel Monat-Tag-Jahr vs. Tag-Monat-Jahr)
Django Janny
1
Funktioniert möglicherweise nicht, abhängig von Ihrem Gebietsschema. Amerikanisch formatierte Daten mit MM-TT-JJJJ funktionieren nirgendwo anders auf der Welt, entweder mit TT-MM-JJJJ (Europa) oder JJJJ-MM-TT (einige Orte in Asien)
Paul
@ Paul, was kann nicht funktionieren? Wie in einem Kommentar geschrieben, kann man Formatierungsoptionen verwenden ...
Betlista
4

Ich würde expr matchanstelle von verwenden =~:

expr match "$date" "[0-9]\{8\}" >/dev/null && echo yes

Dies ist besser als die derzeit akzeptierte Antwort der Verwendung, =~da =~sie auch mit leeren Zeichenfolgen übereinstimmt, was meiner Meinung nach nicht der Fall sein sollte. Angenommen, es badvarist nicht definiert, dann [[ "1234" =~ "$badvar" ]]; echo $?gibt es (falsch) 0, während es expr match "1234" "$badvar" >/dev/null ; echo $?das richtige Ergebnis gibt 1.

Wir müssen den Ausgabewert>/dev/null ausblenden expr match, dh die Anzahl der übereinstimmenden Zeichen oder 0, wenn keine Übereinstimmung gefunden wurde. Beachten Sie, dass sich der Ausgabewert vom Exit-Status unterscheidet . Der Exit-Status ist 0, wenn eine Übereinstimmung gefunden wurde, oder 1, wenn dies nicht der Fall ist.

Im Allgemeinen exprlautet die Syntax für :

expr match "$string" "$lead"

Oder:

expr "$string" : "$lead"

wo $leadist ein regulärer Ausdruck. Es exit statusist wahr (0), wenn es leadmit dem führenden Slice von string(Gibt es einen Namen dafür?) Übereinstimmt . Zum Beispiel expr match "abcdefghi" "abc"Exits true, aber expr match "abcdefghi" "bcd"Exits false. (Dank an @Carlo Wood für den Hinweis.

Penghe Geng
quelle
7
=~stimmt nicht mit leeren Zeichenfolgen überein, Sie vergleichen eine Zeichenfolge mit einem leeren Muster in dem von Ihnen angegebenen Beispiel. Die Syntax lautet string =~ patternund ein leeres Muster passt zu allem.
Bstpierre
2
Dies stimmt nicht mit einer Teilzeichenfolge überein, sondern gibt (auf stdout) die Anzahl der führenden Zeichen zurück, die übereinstimmen, und der Beendigungsstatus ist wahr, wenn mindestens 1 Zeichen übereinstimmt. Aus diesem Grund hat eine leere Zeichenfolge (die 0 Zeichen entspricht) den Beendigungsstatus false. Zum Beispiel expr match "abcdefghi" "^" && echo Matched || echo No match- und expr match "abcdefghi" "bcd" && echo Matched || echo No match- kehren beide zurück "0\nNo match". Wo als Matching "a.*f"wird zurückkehren "6\nMatched". Die Verwendung des '^' in Ihrem Beispiel ist daher ebenfalls unnötig und bereits impliziert.
Carlo Wood
@bstpierre: Hier geht es nicht darum, ob man das Verhalten beim =~Abgleichen leerer Strings rationalisieren kann . Es ist so, dass dieses Verhalten unerwartet sein und Fehler verursachen kann. Ich habe diese Antwort speziell geschrieben, weil ich davon verbrannt wurde.
Penghe Geng
@PengheGeng Unerwartetes Verhalten? Wenn ein Muster keine Definition oder Einschränkungen hat, stimmt es tatsächlich mit allem überein. Das Fehlen eines Musters passt zu allem. Das Schreiben von robustem Code ist die Antwort und rechtfertigt keine schlechte Erklärung.
Anthony Rutledge
@AnthonyRutledge "robuster Code" erfordert die bestmögliche Verwendung der verfügbaren Tools, um versehentliche Codierungsfehler zu vermeiden. In Shell-Code, in dem eine leere Variable jederzeit leicht und versehentlich durch Rechtschreibfehler eingeführt werden kann, halte ich das Abgleichen leerer Variablen nicht für eine robuste Funktion. Anscheinend exprstimmt der Autor von GNU mir zu.
Penghe Geng
0

Wenn die Verwendung eines regulären Ausdrucks hilfreich sein kann, um festzustellen, ob die Zeichenfolge eines Datums korrekt ist, kann sie nicht einfach verwendet werden, um festzustellen, ob das Datum gültig ist. Die folgenden Beispiele übergeben den regulären Ausdruck, sind jedoch alle ungültige Daten: 20180231, 20190229, 20190431

Wenn Sie also überprüfen möchten, ob Ihre Datumszeichenfolge (nennen wir sie datestr) das richtige Format hat, ist es am besten, sie zu analysieren dateund datedie Zeichenfolge in das richtige Format zu konvertieren. Wenn beide Zeichenfolgen identisch sind, haben Sie ein gültiges Format und ein gültiges Datum.

if [[ "$datestr" == $(date -d "$datestr" "+%Y%m%d" 2>/dev/null) ]]; then
     echo "Valid date"
else
     echo "Invalid date"
fi
kvantour
quelle