Vérifiez si une chaîne correspond à une expression régulière dans le script Bash

204

L'un des arguments que mon script reçoit est une date au format suivant: yyyymmdd .

Je veux vérifier si j'obtiens une date valide en entrée.

Comment puis-je faire ceci? J'essaie d'utiliser une expression régulière comme:[0-9]\{\8}

Peter Nijem
la source
Il est facile de vérifier si le format est correct. Mais je ne pense pas que vous puissiez, en bash (avec les intégrés), vérifier si la date est valide.
RedX

Réponses:

317

Vous pouvez utiliser la construction de test, [[ ]]avec l'opérateur de correspondance d'expressions régulières =~, pour vérifier si une chaîne correspond à un modèle d' expression régulière .

Pour votre cas spécifique, vous pouvez écrire:

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

Ou un test plus précis:

[[ $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

Autrement dit, vous pouvez définir une expression régulière dans Bash correspondant au format souhaité. De cette façon, vous pouvez faire:

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

où les commandes après &&sont exécutées si le test réussit et les commandes après ||sont exécutées si le test échoue.

Notez que cela est basé sur la solution d'Aleks-Daniel Jakimenko dans la vérification du format de date d'entrée utilisateur dans bash .


Dans d'autres shells, vous pouvez utiliser grep . Si votre shell est compatible POSIX, faites

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

Dans le poisson , qui n'est pas conforme à POSIX, vous pouvez faire

echo "$date" | grep -Eq "^regex\$"; and echo "matched"; or echo "did not match"
fedorqui 'SO arrête de nuire'
la source
19
J'en suis conscient, mais j'aime aussi prendre en compte qui demande et jusqu'où ils en sont avec bash. Si nous fournissons des conditions très complexes, ils n'apprendront rien et reviendront chaque fois qu'ils auront un autre doute. Je préfère donner une réponse plus compréhensible.
fedorqui 'SO arrête de nuire'
7
Il h. Eh bien, la seule façon d'apprendre est de lire beaucoup de bon code. Si vous donnez du faux code qui est facile à comprendre mais dont l'utilisation n'est pas recommandée, c'est une mauvaise façon d'enseigner. De plus, je suis presque sûr que pour ceux qui viennent de commencer à apprendre bash (connaissant probablement déjà quelques bribes d'une autre langue), ils comprendront la syntaxe bash pour regex plus facilement qu'une grepcommande avec -Eindicateur.
Aleks-Daniel Jakimenko-A.
8
@ Aleks-DanielJakimenko J'ai relu ce post et maintenant je suis d'accord qu'il vaut mieux utiliser bash regex. Merci d'avoir pointé dans la bonne direction, réponse mise à jour.
fedorqui 'SO arrête de nuire'
4
Upvote, qui permet de l'utiliser un peu au-delà de la question OP, pour sh, par exemple ..
Dereckson
3
@ Aleks-DanielJakimenko utilisant grep semble être la meilleure option si vous utilisez sh, fishou d'autres obus moins équipés.
tomekwi
47

Dans la version bash 3, vous pouvez utiliser l'opérateur '= ~':

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

Référence: http://tldp.org/LDP/abs/html/bashver3.html#REGEXMATCHREF

REMARQUE: la citation dans l'opérateur de correspondance entre crochets, [[]], n'est plus nécessaire à partir de la version Bash 3.2

aliasav
la source
20
Vous ne devriez pas utiliser char "lors d'une expression régulière? Parce que quand j'utilise l'expression ne fonctionne pas
Dawid Drozd
De plus, la barre oblique inverse s'échappant de {et} est également problématique.
kbulgrien
32

Un bon moyen de tester si une chaîne est une date correcte est d'utiliser la commande date:

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

du commentaire: on peut utiliser la mise en forme

if [ "2017-01-14" == $(date -d "2017-01-14" '+%Y-%m-%d') ] 
Django Janny
la source
9
Donnez une note élevée à votre réponse car elle permet à la fonction de date de traiter les dates et non les expressions rationnelles sujettes aux erreurs '
Ali
C'est bon pour vérifier les options de date générales, mais si vous devez vérifier un format de date spécifique, peut-il le faire? Par exemple, si je le fais, date -d 2017-11-14eil renvoie le 14 novembre 05:00:00 UTC 2017, mais cela casserait mon script.
Josiah
1
Vous pouvez utiliser quelque chose comme ça: if ["2017-01-14" == $ (date -d "2017-01-14" '+% Y-% m-% d')] Il teste si la date est correcte et vérifiez si le résultat est le même que vos données saisies. Au fait, soyez très prudent avec le format de date localisé (Mois-Jour-Année vs Jour-Mois-Année par exemple)
Django Janny
1
Pourrait ne pas fonctionner, selon votre région. Les dates au format américain utilisant MM-DD-YYYY ne fonctionneront nulle part ailleurs dans le monde, en utilisant soit DD-MM-YYYY (Europe) ou YYYY-MM-DD (certains endroits en Asie)
Paul
@Paul, qu'est-ce qui peut ne pas fonctionner? Comme écrit dans un commentaire, on peut utiliser des options de formatage ...
Betlista
4

J'utiliserais expr matchau lieu de =~:

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

C'est mieux que la réponse actuellement acceptée, =~car =~elle correspondra également à des chaînes vides, ce qui à mon humble avis ne devrait pas. Supposons que badvarn'est pas défini, puis [[ "1234" =~ "$badvar" ]]; echo $?donne (incorrectement) 0, tandis que expr match "1234" "$badvar" >/dev/null ; echo $?donne un résultat correct1 .

Nous devons utiliser >/dev/nullpour masquer expr matchla valeur de sortie de , qui est le nombre de caractères correspondants ou 0 si aucune correspondance n'a été trouvée. Notez que sa valeur de sortie est différente de son état de sortie . Le statut de sortie est 0 si une correspondance est trouvée, ou 1 sinon.

Généralement, la syntaxe de exprest:

expr match "$string" "$lead"

Ou:

expr "$string" : "$lead"

$leadest une expression régulière. Ce exit statussera vrai (0) si leadcorrespond à la tranche principale de string(Y a-t-il un nom pour cela?). Par exemple, expr match "abcdefghi" "abc"quitte true, mais expr match "abcdefghi" "bcd"quitte false. (Nous remercions @Carlo Wood de l'avoir signalé.

Penghe Geng
la source
7
=~ne correspond pas à des chaînes vides, vous comparez une chaîne à un modèle vide dans l'exemple que vous donnez. La syntaxe est string =~ pattern, et un modèle vide correspond à tout.
bstpierre
2
Cela ne correspond pas à une sous-chaîne, il renvoie (à stdout) le nombre de caractères de tête qui correspondent et le statut de sortie est vrai si au moins 1 caractère a été trouvé. C'est pourquoi une chaîne vide (qui correspond à 0 caractère) a un statut de sortie faux. Par exemple expr match "abcdefghi" "^" && echo Matched || echo No match- et expr match "abcdefghi" "bcd" && echo Matched || echo No match- les deux reviennent "0\nNo match". Où que l'appariement "a.*f"reviendra "6\nMatched". L'utilisation du '^' dans votre exemple est donc également inutile et déjà implicite.
Carlo Wood
@bstpierre: il ne s'agit pas ici de savoir si l'on peut rationaliser le comportement de =~correspondance de chaînes vides. C'est que ce comportement peut être inattendu et provoquer des erreurs. J'ai écrit cette réponse précisément parce que j'ai été brûlé par elle.
Penghe Geng
@PengheGeng Comportement inattendu? Si un modèle n'a pas de définition ni de contraintes, il correspond en fait à n'importe quoi. L'absence de motif correspond à tout. Écrire du code robuste est la réponse, ne justifiant pas une mauvaise explication.
Anthony Rutledge
@AnthonyRutledge "code robuste" exige la meilleure utilisation des outils disponibles pour éviter les erreurs de codage accidentelles. Dans le code Shell où une variable vide peut être facilement et accidentellement introduite à tout moment par des erreurs d'orthographe, je ne pense pas que permettre la correspondance de variables vides soit une fonctionnalité robuste. Apparemment, l'auteur de GNU exprest d'accord avec moi.
Penghe Geng
0

Lorsque l'utilisation d'une expression régulière peut être utile pour déterminer si la séquence de caractères d'une date est correcte, elle ne peut pas être utilisée facilement pour déterminer si la date est valide. Les exemples suivants transmettront l'expression régulière, mais sont toutes des dates non valides: 20180231, 20190229, 20190431

Donc, si vous voulez valider si votre chaîne de date (appelons-la datestr) est au format correct, il est préférable de l'analyser avec dateet de demander dateà convertir la chaîne au format correct. Si les deux chaînes sont identiques, vous avez un format et une date valides.

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