Chapitre 914 minDébutant
Expressions régulières : -match, Select-String, grep et sed
Par Josselyn C., prof de BTS SIO. Mis à jour le .
À la fin de ce chapitre, tu sauras
- lire et écrire une expression régulière simple et ancrée ;
- valider, extraire et remplacer avec
-match,-replaceetSelect-String; - chercher dans des journaux avec
grep,sedet[[ =~ ]]; - compter les tentatives de connexion échouées par adresse.
4 exercices corrigés
Une expression régulière (regex) est un motif qui décrit une famille de chaînes de caractères : « PC- suivi de quatre chiffres », « une adresse IPv4 », « une ligne qui contient Failed password ». Elle sert à vérifier une saisie, à chercher des lignes dans un journal et à extraire ou remplacer une partie d'un texte. PowerShell (avec -match, -replace, Select-String) et Bash (avec grep, sed et [[ =~ ]]) utilisent la même syntaxe de base, avec quelques différences que ce chapitre signale.
La syntaxe de base
| Motif | Correspond à | Exemple qui correspond |
|---|---|---|
abc | La suite de caractères abc, n'importe où | srv-abc-01 |
. | N'importe quel caractère (un seul) | s.v : srv, s-v |
\. | Un point, au sens propre (le \ retire le sens spécial) | ad\.sorbier |
[abc], [a-z], [0-9] | Un caractère de l'ensemble ou de l'intervalle | PC-[0-9] : PC-4 |
[^0-9] | Un caractère qui n'est pas dans l'ensemble | |
\d, \w, \s | Un chiffre, un caractère de mot (lettre, chiffre, _), un espace | \d\d : 42 |
^, $ | Début, fin de la chaîne (ou de la ligne) | ^srv- : commence par srv- |
*, +, ? | 0 fois ou plus, 1 fois ou plus, 0 ou 1 fois l'élément précédent | ab+c : abc, abbbc |
{4}, {2,5}, {2,} | Exactement 4 fois, de 2 à 5 fois, au moins 2 fois | \d{4} : 2026 |
(…) | Un groupe, que l'on peut répéter ou récupérer | (ab)+ : ababab |
a|b | a ou b | (Failed|Accepted) |
\b | Une frontière de mot | \broot\b ne trouve pas rootkit |
Ancre ton motif dès que tu valides une saisie : \d{4} trouve quatre chiffres dans PC-004212, alors que ^PC-\d{4}$ exige que la chaîne entière soit PC- suivi d'exactement quatre chiffres.
Les quantificateurs sont gourmands : ".*" sur le texte "a" et "b" prend "a" et "b" en entier, du premier au dernier guillemet. Ajoute ? pour la version paresseuse, qui s'arrête au plus tôt : ".*?" prend "a". La version paresseuse existe en PowerShell et avec grep -P, pas dans les expressions étendues de grep -E et sed -E.
Vérifier avec -match en PowerShell
-match renvoie $true ou $false et, quand la gauche est une seule chaîne, remplit la variable automatique $Matches avec ce qui a été trouvé.
'PC-0042' -match '^PC-\d{4}$' # True
'pc-0042' -match '^PC-\d{4}$' # True : -match ignore la casse
'pc-0042' -cmatch '^PC-\d{4}$' # False : -cmatch la respecte
'PC-00421' -match '^PC-\d{4}$' # False : cinq chiffres
'srv-fichiers.ad.sorbier.example' -match '^(?<hote>[^.]+)\.(?<domaine>.+)$'
$Matches.hote # srv-fichiers
$Matches.domaine # ad.sorbier.example
$Matches[0] # srv-fichiers.ad.sorbier.example : toute la correspondance
(?<nom>…) est un groupe nommé : son contenu se lit dans $Matches.nom. Sans nom, les groupes sont numérotés $Matches[1], $Matches[2] dans l'ordre de leurs parenthèses ouvrantes.
Quand la gauche est un tableau, -match filtre : 'srv-ad', 'srv-web', 'srv-ad2' -match '^srv-ad' renvoie srv-ad et srv-ad2, et ne remplit pas $Matches.
Remplacer et découper
-replace remplace tout ce qui correspond. Dans le texte de remplacement, $1, $2 ou ${nom} reprennent les groupes. Écris ce texte entre apostrophes, sinon PowerShell remplace lui-même $1 par une variable vide avant que la regex ne s'en serve.
'Lefèvre Julie' -replace '^(\w+) (\w+)$', '$2 $1' # Julie Lefèvre
'192.168.10.177' -replace '\.\d+$', '.0' # 192.168.10.0
'Facture du 3/11' -replace '\s+', ' ' # Facture du 3/11
'srv-ad, srv-ad2 ;srv-web' -split '\s*[,;]\s*' # srv-ad, srv-ad2, srv-web
\w reconnaît les lettres accentuées en .NET : Lefèvre est bien un mot. Ce n'est pas le cas partout : en Bash, l'équivalent dépend des réglages de langue du système.
Chercher dans des fichiers avec Select-String
Select-String est le grep de PowerShell : il lit des fichiers (ou des chaînes reçues du pipeline) et renvoie les lignes qui correspondent, avec le nom du fichier, le numéro de ligne et les correspondances.
Voici un extrait d'un journal SSH exporté de srv-web, auth.log :
2026-11-03T02:14:07+01:00 srv-web sshd[4121]: Failed password for invalid user admin from 203.0.113.50 port 51234 ssh2
2026-11-03T02:14:11+01:00 srv-web sshd[4121]: Failed password for invalid user admin from 203.0.113.50 port 51240 ssh2
2026-11-03T02:15:02+01:00 srv-web sshd[4130]: Failed password for root from 198.51.100.23 port 40022 ssh2
2026-11-03T02:15:09+01:00 srv-web sshd[4130]: Failed password for invalid user test from 203.0.113.50 port 51302 ssh2
2026-11-03T07:58:40+01:00 srv-web sshd[5012]: Accepted publickey for mdiallo from 192.168.10.209 port 52114 ssh2: ED25519 SHA256:k3J9
2026-11-03T08:03:15+01:00 srv-web sshd[5077]: Failed password for mdiallo from 192.168.10.209 port 52190 ssh2
2026-11-03T09:41:52+01:00 srv-web sshd[5230]: Failed password for invalid user oracle from 198.51.100.23 port 40188 ssh2
Les adresses 203.0.113.x et 198.51.100.x sont réservées à la documentation : elles jouent ici le rôle d'adresses Internet. Compter les échecs par adresse source :
$motif = 'Failed password for (invalid user )?(?<compte>\S+) from (?<ip>[\d.]+)'
Select-String -Path .\auth.log -Pattern $motif |
ForEach-Object { $_.Matches[0].Groups['ip'].Value } |
Group-Object | Sort-Object Count -Descending | Select-Object Count, Name
Count Name
----- ----
3 203.0.113.50
2 198.51.100.23
1 192.168.10.209
Chaque résultat de Select-String a une propriété Matches, dont les Groups donnent les groupes nommés. -SimpleMatch cherche un texte exact sans regex, -NotMatch inverse la recherche, -CaseSensitive respecte la casse, -Context 2 affiche deux lignes avant et après chaque correspondance.
Sur un serveur Windows, les échecs de connexion ne sont pas dans un fichier texte mais dans le journal de sécurité (événement 4625), que l'on lit avec Get-WinEvent : la regex sert alors à extraire des valeurs du champ Message. L'analyse des journaux de sécurité à grande échelle est le travail d'un SIEM comme Wazuh.
grep, sed et [[ =~ ]] en Bash
grep affiche les lignes qui correspondent à un motif. Ses options les plus utiles :
| Option | Effet |
|---|---|
-E | Expressions régulières étendues : +, ?, {n}, | et les parenthèses sans \ devant |
-P | Expressions compatibles Perl (\d, quantificateurs paresseux), propre à GNU grep |
-i | Ignorer la casse |
-v | Inverser : les lignes qui ne correspondent pas |
-c | Compter les lignes qui correspondent |
-o | N'afficher que la partie qui correspond, une par ligne |
-n | Numéroter les lignes |
-r | Chercher dans tous les fichiers d'un dossier, récursivement |
-q | Ne rien afficher, seulement le code de retour (pour if) |
Le même comptage par adresse qu'en PowerShell :
grep "Failed password" auth.log | grep -oE 'from ([0-9]{1,3}\.){3}[0-9]{1,3}' | cut -d' ' -f2 | sort | uniq -c | sort -rn
3 203.0.113.50
2 198.51.100.23
1 192.168.10.209
Les comptes inexistants que les attaquants ont essayés, sans doublons :
grep -oE 'invalid user [^ ]+' auth.log | cut -d' ' -f3 | sort -u
admin
oracle
test
Sur Debian 13, le journal d'authentification n'est plus un fichier /var/log/auth.log par défaut : il est dans le journal du système, que l'on lit avec journalctl -u ssh et que l'on filtre avec les mêmes grep. C'est la pratique du chapitre 13.
sed modifie un flux de texte ; sa commande s/motif/remplacement/ remplace, et \1, \2 reprennent les groupes. Avec -E, les parenthèses forment des groupes sans \.
sed -E 's/^([0-9-]+)T([0-9:]+)\+01:00 ([^ ]+) .*from ([0-9.]+).*/\1 \2 \3 \4/' auth.log | head -2
2026-11-03 02:14:07 srv-web 203.0.113.50
2026-11-03 02:14:11 srv-web 203.0.113.50
sed -i modifie un fichier sur place : sed -i 's/^PermitRootLogin yes/PermitRootLogin no/' /etc/ssh/sshd_config. Garde une copie avant (sed -i.bak … crée sshd_config.bak), et vérifie le résultat : une regex trop large modifie des lignes que tu ne voulais pas toucher.
Dans un script, [[ chaîne =~ regex ]] teste une regex étendue et range les groupes dans le tableau BASH_REMATCH :
ligne='Failed password for root from 198.51.100.23 port 40022 ssh2'
motif='for (invalid user )?([^ ]+) from ([0-9.]+)'
if [[ $ligne =~ $motif ]]; then
echo "compte=${BASH_REMATCH[2]} ip=${BASH_REMATCH[3]}"
fi
compte=root ip=198.51.100.23
Range toujours le motif dans une variable et écris $motif sans guillemets à droite de =~ : entre guillemets, Bash chercherait le texte littéral et non la regex.
Des motifs prêts à l'emploi
| Pour reconnaître | Motif (ancré) | Remarque |
|---|---|---|
| Un login Sorbier (initiale et nom, chiffre facultatif) | ^[a-z]{2,20}[0-9]?$ | Minuscules sans accents, comme la convention de nommage |
| Un numéro d'inventaire | ^PC-[0-9]{4}$ | |
| Une adresse MAC | ^([0-9A-Fa-f]{2}[:-]){5}[0-9A-Fa-f]{2}$ | Accepte : et - |
| Une adresse IPv4 (forme) | ^([0-9]{1,3}\.){3}[0-9]{1,3}$ | Accepte 999.1.1.1 : vérifie ensuite chaque octet ≤ 255, ou utilise [ipaddress] en PowerShell |
| Une date au format ISO | ^[0-9]{4}-[0-9]{2}-[0-9]{2}$ | La forme seulement : 2026-13-45 passe |
| Une ligne de commentaire ou vide | ^\s*(#|$) | Pour sauter ces lignes dans un fichier de configuration |
Exercices
Exercice 1 sur 4 Écrire le motif Réussi À revoir
Écris l'expression régulière, ancrée, qui reconnaît :
- un nom de serveur Sorbier :
srv-suivi de lettres minuscules, et éventuellement d'un chiffre final (srv-ad,srv-ad2,srv-fichiers) ; - un nom de compte d'administration :
adm, un chiffre de 0 à 2, un tiret, puis un login (adm0-amercier) ; - une ligne de journal pfSense ou Linux qui contient le mot
blockou le motdeny, quelle que soit la casse (en PowerShell).
Voir la réponseMasquer la réponse de l'exercice 1
^srv-[a-z]+[0-9]?$^adm[0-2]-[a-z]{2,20}[0-9]?$'\b(block|deny)\b'avec-match, qui ignore déjà la casse :$ligne -match '\b(block|deny)\b'. Pas d'ancre ici : on cherche le mot n'importe où dans la ligne.\bévite de trouverblockedoudenying; si tu veux aussi ces formes, retire les\b.
Tu avais trouvé ?
Exercice 2 sur 4 Prédire la sortie Réussi À revoir
Donne ce qu'affiche chaque ligne PowerShell.
'PC-0042' -match '\d{4}'
'PC-004212' -match '^PC-\d{4}$'
'srv-web' -replace 'web', 'bdd'
'Diallo Moussa' -replace '^(\w+) (\w+)$', '$2.$1'
'a1b22c333' -replace '\d+', '#'
('srv-ad;srv-ad2;;srv-web' -split ';').Count
Voir la réponseMasquer la réponse de l'exercice 2
True
False
srv-bdd
Moussa.Diallo
a#b#c#
4
La première ligne est vraie car le motif n'est pas ancré : 0042 se trouve dans la chaîne. La deuxième est fausse : ^…$ exige exactement quatre chiffres. \d+ prend chaque suite de chiffres en entier, d'où un seul # par suite. Les deux points-virgules collés produisent un élément vide : srv-ad, srv-ad2, une chaîne vide et srv-web, soit 4 éléments.
Tu avais trouvé ?
Exercice 3 sur 4 Analyser le journal SSH Réussi À revoir
Avec le fichier auth.log de ce chapitre, écris les commandes Bash qui :
- comptent les échecs de mot de passe ;
- affichent la ligne de la connexion réussie ;
- affichent les comptes visés par les échecs, sans le « invalid user », triés et sans doublons.
Donne la sortie de chaque commande. Que conclus-tu sur les adresses et sur le compte mdiallo ?
Voir la réponseMasquer la réponse de l'exercice 3
grep -c "Failed password" auth.log
grep "Accepted" auth.log
grep "Failed password" auth.log | sed -E 's/.*Failed password for (invalid user )?([^ ]+) from.*/\2/' | sort -u
6- La ligne de 07:58:40 :
… Accepted publickey for mdiallo from 192.168.10.209 port 52114 ssh2: ED25519 SHA256:k3J9. - Cinq comptes :
admin
mdiallo
oracle
root
test
Les adresses 203.0.113.50 et 198.51.100.23, extérieures, essaient des comptes génériques (admin, root, test, oracle) : c'est une attaque par force brute ou par dictionnaire, typique d'un serveur SSH exposé. L'échec de mdiallo vient du poste d'administration (192.168.10.209), quelques minutes après une connexion réussie par clé : sans doute une erreur de saisie, à vérifier avec lui. La vraie correction est en amont : SSH ne devrait pas être joignable depuis Internet, seulement depuis le VLAN d'administration, avec une authentification par clé et Fail2ban (cours Cybersécurité des infrastructures).
Tu avais trouvé ?
Exercice 4 sur 4 Valider des saisies en Bash Réussi À revoir
Écris une boucle qui teste les valeurs PC-0042, PC-42, pc-0042 et PC-00421 avec [[ =~ ]] et le motif des numéros d'inventaire, et affiche « valide » ou « invalide » pour chacune. Donne la sortie.
Voir la réponseMasquer la réponse de l'exercice 4
motif='^PC-[0-9]{4}$'
for n in PC-0042 PC-42 pc-0042 PC-00421; do
if [[ $n =~ $motif ]]; then echo "$n : valide"; else echo "$n : invalide"; fi
done
PC-0042 : valide
PC-42 : invalide
pc-0042 : invalide
PC-00421 : invalide
Contrairement à -match en PowerShell, =~ respecte la casse : pc-0042 est refusé. Pour ignorer la casse en Bash, on active shopt -s nocasematch avant le test.
Tu avais trouvé ?