jeudi 18 novembre 2021
Ce billet a été vu pour la première fois sur le blog de Synbioz le 18 November 2021 sous licence CC BY-NC-SA.

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 !