arthur.bebou

Le site arthur.bebou.netlib.re - retour accueil

git clone git://bebou.netlib.re/arthur.bebou

Log | Files | Refs |

commit 87b237406f00fdfcac1c2ef272d71e7d8a5af254
parent 4e35bece86990586776fe03ee3e31af2cf459a70
Auteurice: Arthur Pons <arthur.pons@unistra.fr>
Date:   Wed, 14 May 2025 22:29:12 +0200

Refonte article avoid software, il était confus

Diffstat:
Mcontents/avoid-software-commentaires/index.sh | 239+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++--
1 file changed, 234 insertions(+), 5 deletions(-)

diff --git a/contents/avoid-software-commentaires/index.sh b/contents/avoid-software-commentaires/index.sh @@ -25,14 +25,243 @@ nommée [Avoid Software](https://avoidsoftware.sarahgarcin.com/index.html). Cet article a pour but d'être une contribution à ce fanzine : * en apportant des précisions sur le fonctionnement des scripts - * en proposant des alternatives aux scripts lorsqu' - * ils ne sont pas POSIX - * ils ne fonctionnent pas sur les fichiers contenant des espaces - * garantissant que tout fonctionne sous OpenBSD + * en proposant des alternatives aux scripts lorsqu'ils ne fonctionnent pas + dans certains cas + * en garantissant que tout fonctionne sous OpenBSD * en proposant une partie expliquant comment transformer les commandes en scripts qui peuvent prendre des arguments -## Les dépendances +## Les limites fréquemment rencontrés + +Les scripts de ce zine contiennent des limites récurrentes, à savoir : + + * La mauvaise gestion des fichiers commençant par `-` + * La mauvaise gestion des fichiers contenant des espaces + * La mauvaise gestion des cas où aucun fichier ne correspond au critère de + recherche + * Ne pas être "portable", c'est-à-dire ne pas pouvoir fonctionner sur un large + ensemble de shell et de systèmes d'exploitation + +Avant de rentrer dans les détails de ces limites il faut ête clair. Tous les +logiciels, et en particulier les scripts de ce zine, n'ont pas vocation à être +parfaitement corrects. Leur usage est généralement situé, sur un OS particulier +pour une personne particulière dans un dossier particulier. Dans la plupart des +contextes les limites de ces scripts ne sont pas pertinentes. Il n'importe pas +qu'un script ne gère pas les fichiers dont les noms comportent des espaces si +l'on *sait* qu'on l'utilise sur des fichiers dont les noms ne comportent *pas* +d'espace. Cela dit puisque ce zine est publique et puisque j'aime apprendre des +choses sur le shell je pense qu'il est opportun d'en chercher et partager des +alternatives plus robustes. + +### La mauvaise gestion des fichiers commençant par `-` + +Imaginons que nous voulons lancer une commande sur plusieurs fichiers d'un +dossier à la fois.Par exemple, lister tous les jpeg. Pour cela on peut utiliser +les "globs" : + + $ ls *.jpeg + machin.jpeg + truc.jpeg + +Dans les globs le caractère `*` veut dire "n'importe quel caractère autant de +fois que nécessaire". Donc `*.jpeg` veut dire "tous les fichiers se terminant +par `.jpeg` et, pour peu que les fichiers soient correctement nommés, adresser +tous les jpeg d'un dossier. On peut penser les globs comme des expressions +régulières beaucoup moins puissantes. + +D'autres caractères des globs intéressants : + + * `?` : "n'importe quel caractère une fois" + * `[]` : introduit ce que l'on appelle une "classe de caractère". En écrivant + `[abc]` on dit "une fois le caractère a, b ou c". Il est également possible + d'utiliser le caractère `-` pour décrire une étendue de caractère. `[3-8]` + ou `[d-h]` voudront dire "une fois un entier entre 3 et 8 inclus" et "une + fois une lettre minuscule entre d et h dans l'ordre alphabétique inclus". + * `!` : dans une classe de caractère permet de prendre le complément des + caractères. `[!abc]` veut dire "une fois n'importe quel caractère *sauf* a, + b ou c". + +On peut donc étendre la commande précédente pour lui faire des choses plus +précises et alambiquées comme : + + $ ls *-[!1][0-9][0-9]-??.jpeg + +convertir tous les fichiers commençant n'importe comment, suivi d'un tiret, +suivi de n'importe quel caractère n'étant pas un `1` suivi de deux entiers suivi +d'un tiret suivi d'exactement deux caractères suivi de `.jpeg`. + +En arrière plan le shell "développe" le glob en le remplaçant par tous les +chemins auxquels il correspond. Si l'on a trois fichiers dans notre dossier +`a.jpg`, `b.jpg` et `machin.jpg` la commande + + ls ?.jpg + +va s'étendre en + + ls "a.jpg" "b.jpg" + +avant de s'éxecuter. Il est possible dans certains shell à l'écriture de la +commande d'obtenir un retour des fichiers qui correspondent en appuyant sur la +touche tabulation comme si l'on voulait compléter le glob. Avec ma configuration +de `zsh` le développement se fait carrément en direct dans le prompt. + +Si vous êtes particulièrement alerte ce fonctionnement par réécriture de +commande devrait vous donner envie de tester un truc. Que se passe-t-il si un +le nom d'un fichier commence par `-` ? Admettons qu'il existe un fichier nommé +`-azerty.jpg` et que l'on utilise le glob `*.jpg` en argument de la fonction +`ls` : + + $ ls *.jpg + # devient + $ ls -azerty.jpg + ls : option invalide -- 'z' + +On se retrouve avec une erreur. Et pour cause, la commande a cru que +`-azerty.jpg` est la déclaration d'options de la commande `ls`. C'est d'autant +plus facile de tomber sur cette erreur que le tiret `-` arrive avant la plupart +des autres caractères dans l'ordre alphanumérique. + +Il y a deux manières de se prémunir de cette erreur. La manière la plus +universelle est d'ajouter `./` devant le glob. `./` voulant dire "le dossier +courant" le glob se développera sur exactement les mêmes fichier qu'auparavant +mais les chemins démarreront tous `./` : + + $ ls ./-azerty.jpg + -azerty.jpg + +Il n'y a donc plus d'ambiguité entre les options et les noms des fichiers. Une +deuxième solution est d'ajouter `--` entre les options et les arguments de la +commande : + + $ ls -larth -- *.jpg + # devient + $ ls -larth -- -azerty.jpg + -rw-r--r-- 1 arthur arthur 0 10 mai 11:27 -azerty.jpg + +Ici `--` permet à la commande de savoir que tout ce qu'il suit doit être +interpréter comme des arguments et non pas comme de potentielles options. Bien +que [cette syntaxe soit +POSIX](https://pubs.opengroup.org/onlinepubs/9699919799/basedefs/V1_chap12.html#tag_12_02) +toutes les commandes ne respectent pas ce principe, en particulier les commandes +qui ne sont pas "de base" sur les systèmes Unix. La première solution est donc +préférable. + +### La mauvaise gestion des fichiers dont le nom contient des espaces + +Dans les systèmes UNIX supportant UTF-8 les noms des fichiers peuvent contenir +tous les caractères sauf le caractère nul (`\0`). Écrire des scripts qui gèrent +correctement les fichiers en toutes circonstances nécessite de faire attention +à la manière dont le shell interprète les blancs (espace, tabulation, retour à +la ligne etc). Les blancs, dans la pratique presque toujours les espaces, +servent au shell à distinguer les éléments les un des autres. C'est la raison +pour laquelle il est important de toujours mettre un ou plusieurs espaces entre +les noms des commandes et leurs options et arguments : + + $ ls -la fichier.pdf + +Comme on le voit dans la commande ci-dessus il peut y avoir des exceptions. Les +options peuvent être combinées (`-la`). Lorsque les options peuvent prendre des +arguments on peut généralement coller les deux puisqu'il n'y a pas d'ambiguité +sur la découpe comme l'option `-i` qui est nécessairement suivi d'un suffixe, +espace ou pas : + + $ sed -i.bak 's/machin/truc/' *.txt + +Si les espaces sont si importants on peut se demander quel est leur impact sur +la manière de comprendre les arguments quand ils sont des chemins de fichiers. +Imaginons que l'on a deux pdf `fichier.pdf` et `fichier 2.pdf`. Si l'on veut +afficher des informations à leur propos on risque de tomber sur une erreur : + + $ ls -la fichier.pdf fichier 2.pdf + ls: impossible d'accéder à 'fichier': Aucun fichier ou dossier de ce type + ls: impossible d'accéder à '2.pdf': Aucun fichier ou dossier de ce type + -rw-r--r-- 1 arthur arthur 365368 9 avril 15:00 fichier.pdf + +On voit que le shell a découpé les arguments en trois fichier au lieu de deux. +Il n'est pas assez sophistiqué pour tester si `fichier` et `(1).pdf` sont en +réalité deux parties du nom d'un même fichier. Pour faire comprendre au shell +que cet espace ne démarque pas la frontière entre deux éléments différents il +faut ajouter un autre marqueur. L'objectif est de faire en sorte que l'espace +soit compris littéralement. Il y a deux manière de le faire : + + * Englober le chemin entre deux guillemets `"` ou apostrophes `'` : "fichier + 2.pdf" + * Echapper l'espace avec un `\` : fichier\ 2.pdf + + $ ls -la fichier.pdf "fichier 2.pdf" + -rw-r--r-- 1 arthur arthur 365368 9 avril 15:00 fichier.pdf + -rw-r--r-- 1 arthur arthur 365368 9 avril 15:00 fichier 2.pdf + +Lors d'un usage interactif du shell, en écrivant directement après le prompt, ce +problème est rarement rencontré puisque l'opérateurice un peu expérimentée +utilisera l'auto-complétion et/ou les globs. Par exemple l'auto-complétion de +zsh pré-echappe les espaces et affichera les résultats : + + $ ls fich<TAB> + fichier\ 1.pdf fichier.pdf + $ ls fichier* + 'fichier 1.pdf' fichier.pdf + +On remarque ici que le glob est immunisé à la malédiction des espaces dans les +noms de fichier. Ce n'est pas par hasard que dans le titre précédent j'ai +remplacé `ls ?.jpg` par `ls "a.jpg" "b.jpg"`. La raison pour laquelle les globs +ne rencontrent pas de problème est parce qu'ils sont interprétés *après* le +découpage des éléments par le shell. Le shell découpe `ls fichier*` en deux +éléments. Il détecte que le second est un glob qu'il développe en `fichier.pdf` +et `fichier 1.pdf`. A ce stade le shell a déjà découpé les éléments de la +commande, il n'y a donc plus de risque de croire que l'espace dans le deuxième +fichier sépare deux fichiers différents. Finalement il exécute `ls` avec les deux +bons arguments. + +Alors si les globs sont immunisé et si l'on fait rarement l'erreur d'écrire à la +main des chemins contenant des espaces, quand est-ce que cela pose problèmes ? +Lorsque l'on dépasse la "simple" commande et que l'on se met à utiliser des +variable ou à combiner des sorties de commandes avec d'autres éléments de +syntaxe. + +Imaginons vouloir mettre la valeur `fichier 1.pdf` dans une variable : + + $ file=fichier 1.pdf + sh: 1: 1.pdf: not found + $ echo $file + + + +Le shell a découpé cette commande en deux partie : la déclaration de la variable +`file` et la tentative d'exécution de la commande inexistante `1.pdf`. Plutôt +cohérent avec ce que l'on a appris jusque là. Ce qui est peut-être plus +surprenant c'est que la variable "$file" est vide. Et pour cause la syntaxe +`var=valeur cmd` est particulière. Elle ne déclare la variable `var` que pour +l'exécution de la commande `cmd`. Elle n'existera plus directement après. Cette +syntaxe n'est donc pas à confondre avec `var=valeur;cmd` qui fera les deux +opération successivement et ainsi aura durablement créé la variable `var` pour +la suite. Bref c'était pas le propos. + +On peut corriger notre affaire très simplement en faisant `file="fichier +1.pdf"`. Maintenant on veut utiliser la variable plus tard dans le script : + + $ file="fichier 1.pdf" + $ ls $file + ls: impossible d'accéder à 'fichier': Aucun fichier ou dossier de ce type + ls: impossible d'accéder à '1.pdf': Aucun fichier ou dossier de ce type + +On retombe sur notre souci de découpage ! C'est parce que contrairement au +développement des globs le développement des variables se fait *avant* le +découpage des commandes. `ls $file` devient alors `ls fichier 1.pdf` et la +commande `ls` sera donc exécutée avec deux arguments séparés. Pour y remédier il +faut préemptivement protéger la variable avec des guillemets - et non pas des +apostrophes qui ont pour comportement de ne pas étendre les variables à +l'intérieur mais de considérer tout littéralement : + + $ ls "$file" + deviendra + $ ls "fichier 1.pdf" + 'fichier 1.pdf' + +C'est le premier grand enseignement de toute cette affaire. Il faut *toujours* +englober ses variables avec des `"` par défaut et, dans de rares cas, ne pas le +faire lorsque l'on veut que leurs contenus soient découpés en plusieurs éléments +par le shell. ## Les scripts