Regex, ZSH & Darwin
Comme promis dans l’article portant sur les classes de caractères des regex, me revoilà avec, cette fois, un exemple concret que je vais exécuter froidement devant vous !
En passant d’un macOS à une Debian GNU/Linux, je me suis aperçu d’un comportement inattendu sur un script ZSH de ma confection en ce qui concerne les regex, et plus particulièrement les classes de caractères.
Le contexte
L’idée était de détecter la présence d’un mot dans une chaîne de caractères. Prenons la chaîne suivante en exemple :
bat exa fd fzf git htop ncdu neovim ripgrep tig tldr tmux tree watch z zplug
Mettons maintenant que nous recherchions la présence du mot « tldr » dans cette liste. D’après ce que nous savons des classes de caractères, nous pouvons par exemple écrire la regex suivante :
/[[:\<:]]tldr[[:\>:]]/
Les classes de caractères [[:\<:]]
et [[:\>:]]
représentent respectivement
le début et la fin d’un mot. Cela nous permet de nous assurer de ne pas tomber
sur une suite de caractères au milieu d’un mot. On peut ainsi rechercher « z »
sans tomber sur « fzf » ou « zplug », pour reprendre notre exemple.
Lost in the Shell
Voyons à présent ce que cela donne quand on utilise notre petite regex dans le contexte de ZSH.
❯ uname
Darwin
❯ [[ "bat tldr zplug" =~ [[:\<:]]man[[:\>:]] ]] && echo "true" || echo "false"
false
❯ [[ "bat tldr zplug" =~ [[:\<:]]tldr[[:\>:]] ]] && echo "true" || echo "false"
true
Tout semble se passer pour le mieux ! Essayons sous GNU/Linux :
❯ uname
Linux
❯ [[ "bat tldr zplug" =~ [[:\<:]]tldr[[:\>:]] ]] && echo "true" || echo "false"
zsh: failed to compile regex: Nom de classe de caractères invalide
false
Outch ! Mais que se passe-t-il ?
Let’s Read The Famous Manual!
Un petit tour dans la documentation de ZSH devrait nous aiguiller… voyons voir.
REMATCH_PCRE
If set, regular expression matching with the =~ operator will use
Perl-Compatible Regular Expressions from the PCRE library. (The zsh/pcre
module must be available.) If not set, regular expressions will use the
extended regexp syntax provided by the system libraries.
Il semblerait qu’une option nous permettrait d’imposer une bibliothèque compatible Perl (PCRE). Si cette option n’est pas définie, nous sommes dépendants de la bibliothèque système. Allons-y !
❯ uname
Darwin
❯ setopt rematch_pcre
❯ [[ "bat tldr zplug" =~ [[:\<:]]man[[:\>:]] ]] && echo "true" || echo "false"
false
❯ [[ "bat tldr zplug" =~ [[:\<:]]tldr[[:\>:]] ]] && echo "true" || echo "false"
true
❯ unsetopt rematch_pcre
❯ [[ "bat tldr zplug" =~ [[:\<:]]tldr[[:\>:]] ]] && echo "true" || echo "false"
true
❯ uname
Linux
❯ setopt rematch_pcre
❯ [[ "bat tldr zplug" =~ [[:\<:]]man[[:\>:]] ]] && echo "true" || echo "false"
false
❯ [[ "bat tldr zplug" =~ [[:\<:]]tldr[[:\>:]] ]] && echo "true" || echo "false"
true
❯ unsetopt rematch_pcre
❯ [[ "bat tldr zplug" =~ [[:\<:]]tldr[[:\>:]] ]] && echo "true" || echo "false"
zsh: failed to compile regex: Nom de classe de caractères invalide
false
Parfait, ça semble faire le boulot ! Cela dit, si nous nous rappelons bien du tableau présenté dans l’article précédent, il existe d’autres manières de délimiter un mot :
❯ uname
Linux
❯ unsetopt rematch_pcre
❯ [[ "bat tldr zplug" =~ "\<tldr\>" ]] && echo "true" || echo "false"
true
❯ [[ "bat tldr zplug" =~ "\btldr\b" ]] && echo "true" || echo "false"
true
Mais là, manque de chance, c’est macOS qui flanche :
❯ uname
Darwin
❯ unsetopt rematch_pcre
❯ [[ "bat tldr zplug" =~ "\btldr\b" ]] && echo "true" || echo "false"
false
❯ [[ "bat tldr zplug" =~ "\<tldr\>" ]] && echo "true" || echo "false"
false
La solution
À la vue de ces comportements bigarrés, la meilleure option qui s’offre à nous est de nous assurer qu’un moteur PCRE sera utilisé, ou d’utiliser une regex de repli dans le cas contraire. Ce qui pourrait donner ceci :
if [[ -o rematchpcre || "$OSTYPE" == darwin* ]]; then
[[ "bat tldr zplug" =~ [[:\<:]]tldr[[:\>:]] ]]
else
[[ "bat tldr zplug" =~ "\<tldr\>" ]]
fi
On considère ici que si l’option ZSH rematchpcre
est activée ou si le système
d’exploitation est macOS (darwin
de son petit nom), alors on pourra utiliser
notre regex compatible Perl.
En espérant que ce petit retour d’expérience vous aura appris une ou deux choses et donné l’envie de lire le fameux manuel quand une question vous taraude !