Archives de catégorie : Regex

Extraire le nom du serveur avec regex

Voici comment extraire le nom du serveur d’un path complet donné:

Nous pouvons avoir un path ainsi:

Au lieu de partir sur des splits couteux, nous pouvons faire le regex suivant:

Explication du regex:

  • Littéralement \ deux fois
  • ( ) groupe de capture
  • .+ n’importe quel caractère 1 ou plusieurs fois
  • +? le moins de fois possible, pour ne récupérer que la première instance du résultat (le nom du serveur)
  • Littéralement \

Résultat :

 

Opérations booléennes avec les Expressions Régulières

Mon collègue Martial Bornet vient de publier un excellent livre sur les Expressions Régulières (Regex). C’est important de connaître les Regex. Powershell et .NET permettent de les utiliser comme nous allons le voir dans cet article, donc je vous conseille son livre car, car bien connaître les expressions régulières permet souvent de se sortir de situations compliquées et rendre nos chers scripts Powershell bien plus simples.

Les Regex permettent de chercher, comparer et manipuler des strings, et pour illustrer tout ça, nous allons voir comment faire des opérations booléennes avec des expressions régulières.

Petit rappel:

Une opération booléenne est une opération de logique qui utilise les opérateurs Et (-And), Ou (-Or) et not (!) pour vérifier la véracité d’une expression.

Par exemple:

  • L’expression « A et B » est vraie si A est vrai et B est vrai
  • « A ou B » est vraie si A est vrai ou B est vrai
  • !A est vrai si A est égal a 0 ou a null.

Voyons ça avec des exemples concrets.

En Powershell pour savoir si une chaîne de caractères contient une substring je pourrais appeler la méthode Contains, par exemple:

nous pourrions aussi utiliser une opération logique pour vérifier qu’une chaîne contient deux mots: « Hello » et « World »

Mais comment extraire le texte entre « Hello » et « World dans « Hello Magic World »‘, et ne le faire que si le texte est entouré de « Hello » ET de « World ».

Nous pourrions envisager un Split, et vérifier que les éléments 0 et 2 contiennent les chaînes « Hello » et « World », du coup l’élément 1 contient ce qu’on veut extraire, ainsi:

Mais en fait ce split du caractère espace n’est valable que si ce qu’il y a entre Hello et World ne contient pas d’espaces, car le cas échéant ça ne fonctionnerait plus.

Avec plusieurs espaces on devrait utiliser une autre méthode: IndexOf, ainsi:

Vous voyez, dès qu’on veut faire un peu plus, le code devient plus complexe.

Voici une autre façon de faire, en utilisant une expression régulière:

et en une seule ligne s’il vous plaît:

et le résultat, c’est Magic, bien sur.

Voyons un peu cette expression régulière de plus près.

  • (?<=Hello) : est un Positive lookbehind, en français une recherche de droite à gauche, on peut la reconnaître grace au caractère « < » qui nous montre la direction arrière.
  • (?=World) est un Positive Lookahead, une recherche de gauche vers la droite, ici on cherche la première occurrence de « Word ».
  • (.+) est un groupe de capture, c’est le texte qu’on veut extraire: « . » veut dire n’importe quel caractère à part un newline (\n) et + veut dire 1 ou plusieurs fois.

Ce regex peut être interprété ainsi:

Extraire un ou plusieurs caractères si ces caractères sont précédés par Hello et suivis par World.

Voici un Regex permettant d’évaluer une chaîne si elle contient au moins une de deux substrings prédéfinies:

Voyons le Regex:

  • (?=.*One) Cherche depuis le début de la chaîne le mot « One »
  • | opérateur Or (ou)
  • (?=.*Two) Cherche depuis le début de la chaîne le mot « Two »

Si l’une des deux opérandes est vraie, la valeur de $matches[0] est rendu, ce qui est dans ce cas la chaîne complète.

A noter qu’il est important de chercher la chaîne complète (le ‘.*’ après le ‘=’) car Regex se souvient de sa position, et nous voulons par exemple que le résultat soit le même pour la chaîne: « two OR one ».

Voyons maintenant une opération NOT

  • ^ recherche depuis le début
  • (?!.*one.*) one ne doit pas apparaître ni seul, ni accompagné d’autres caractères au début ou à la fin
  • (?!.*two.*) même chose que ‘one’
  • le tout avec .* c’est à dire qu’on s’attend à la présence d’autres caractères dans la chaîne.

En plus simple peut-être:  Ni ( *one*) Ni (*two*) AVEC *

Il existe également un token de recherche négative arrière, le negative lookbehind: (?<!….) mais pour celui là je vous laisse le découvrir dans le livre de Martial 🙂

Voici un lien très utile lorsqu’on veut tester des regex: https://regex101.com/

 

Extraire des caractères avec Regex

Imaginons la chaine de caractères suivante:

cette string est composée d’une suite « abcdef » ensuite de « ID.1234567 » et finalement d’une autre suite de caractères « ABCDEF ». Ce que contiennent les suites ne nous intéresse pas. Ce que nous voulons c’est extraire la chaine ID.1234567, et nous allons le faire avec Regex:

Le regex peut être interprété ainsi:
Lettres I et D suivies du point (\., qui veut dire littéralement un point, car ‘.’ veut dire n’importe quel caractère, donc on utilise un \ pour indiquer que nous voulons littéralement le caractère). Ensuite n’importe quelle caractère(‘.’), sept fois {7}

Résultat:

ID.1234567

Maintenant essayons de sortir la valeur 1234567 de la string trouvée. Pour faire ceci nous pouvons nous appuyer sur une capture regex, qui s’écrit de la sorte:

La capture est définie par les parenthèses. Donc ce qu’on fait ici c’est de trouver la string qui nous intéresse mais nous capturons une portion de la string, voici le résultat:

A noter que l’élément 1 de l’array $Matches contient la string capturée (alors que l’élément 0 contient la string trouvée toute entière.

Remplacer des caractères avec Regex

Imaginons la chaîne de caractères suivante:

Maintenant utilisons l’opérateur -replace pour remplacer les caractères « ef » avec les caractères « ** ».

Ceci nous donne le résultat suivant: « abc** d ** e ** 123 ». Toutes les instances de « ef » sont remplacées.

Or si nous ne voulions pas remplacer toutes les instances de « ef » mais que la première, nous pouvons le faire ainsi:

Cette commande transforme la chaîne ‘ef’ en un objet regex, et appelle la méthode Replace de la classe. Cette méthode prend trois arguments:

  1. la chaîne input à evaluer
  2. la chaîne utilisée pour remplacer les occurrences trouvées
  3. le nombre d’occurrences à remplacer

Cette commande nous donne le résultat suivant: « abc** d ef e ef 123 »

Maintenant essayons de ne remplacer que la dernière occurrence de ‘ef’ dans la chaîne.

Pour ceci nous allons utiliser une expression régulière un peu plus compliquée:

 Cette expression ‘ef(?!.*ef)’ veut dire ceci: remplacer la chaîne ‘ef’ dont les caractères suivants ne contiennent pas à nouveau la chaîne ‘ef’, donc la dernière: (?!.*ef)

Et le résultat est : « abcef d ef e ** 123 »

Partager ce contenu

Regex à la rescousse de Get-ChildItem

Imaginons le problème suivant.

Nous voulons que Get-ChildItem nous retourne les éléments de deuxième niveau, d’une arborescence de répertoires.

Par exemple je peux avoir un répertoire racine C:\TEMP, dans lequel il y a C:\TEMP\LEVEL1 et dans lequel il y a un autre répertoire C:\TEMP\LEVEL1\LEVEL2.

Et bien je voudrais trouver le moyen avec Get-ChildItem de ne récupérer que les répertoires de deuxième niveau en dessous d’un répertoire racine donné.

Lorsque je tombe sur ce type de problématique j’appelle les expressions régulières (REGEX) à la rescousse.

Les Regex sont un langage dans le langage, et surtout un moyen très puissant pour analyser une chaine de caractères.

Créons notre regex:

La chaine de caractères que nous voulons matcher avec notre regex est la suivante: ‘ C:\TEMP\LEVEL1\LEVEL2’.

Il faut la décomposer (mentalement) pour trouver des délimiteurs. Le délimiteur naturel d’une arborescence est le caractère ‘\’ (backslash). Donc nous avons:

x\x\x\x où x est une chaine de caractères arbitraire. Mais attention pas simplement du alphanumérique, car on peut trouver le caractère ‘:’ par exemple. Et le nom d’un répertoire peut contenir des espaces. On peut traduire x avec ‘.*’ qui veut dire, ‘.’ n’importe quel caractère, et ‘*’ répété n fois.

On commence un regex par ^ ce qui signifie début de chaine de caractères. ^ peut aussi vouloir dire not si utilisé au milieu d’une chaine, mais dans notre cas on ne s’en servira pas ainsi.

Du coup pour matcher C:\TEMP nous pouvons écrire ‘^.*\\.*$’

Le ‘$’ veut dire fin de phrase.

Le double backslash sert à dire que nous voulons vraiment un backslash dans la chaine. Le backslash étant un moyen d’indiquer des caractères spécifiques, \s est un string, \d est un chiffre, \t est un Tab, \n est nouvelle ligne, \0 est le null character utilisé en C pour finir une chaine de caractères. Du coup si nous voulons un \ nous devons en mentionner deux.

Voici le regex complet:

La structure du code est la suivante:

Nous faisons un appel à Get-ChildItem en indiquant un répertoire racine, et en indiquant -recurse pour récupérer les répertoires enfants. Ensuite on ‘pipe’ le résultat vers la cmdlet Where-Object où on analysera le nom de chaque élément avec notre regex.

Finalement on ‘pipe’ le résultat vers ForEach-Object où l’on pourra traiter chaque élément de façon individuelle avec la référence $_.

Voici le code complet: