arthur.bebou

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

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

Log | Files | Refs |

commit ba66952e3fef7b2624f46999741276c57369c456
parent 87b237406f00fdfcac1c2ef272d71e7d8a5af254
Auteurice: Arthur Pons <arthur.pons@unistra.fr>
Date:   Thu, 15 May 2025 12:35:01 +0200

Refonte de l'article avoid software

Diffstat:
Mcontents/avoid-software-commentaires/index.sh | 546+++++++++++++++++++++++++++++++++++++------------------------------------------
1 file changed, 255 insertions(+), 291 deletions(-)

diff --git a/contents/avoid-software-commentaires/index.sh b/contents/avoid-software-commentaires/index.sh @@ -13,6 +13,8 @@ de [cet article](https://dwheeler.com/essays/filenames-in-shell.html) vraiment détaillé. Merci à David A. Wheller qui se trouve avoir le même nom que [le premier détenteur d'un doctorat en informatique](https://fr.wikipedia.org/wiki/David_Wheeler_(informaticien)). +Merci aussi au goat Stéphane Chazelas et toutes ses réponses sur +[stackexchange](https://unix.stackexchange.com/users/22565/st%c3%a9phane-chazelas?tab=answers). ## Introduction @@ -254,164 +256,206 @@ apostrophes qui ont pour comportement de ne pas étendre les variables à l'intérieur mais de considérer tout littéralement : $ ls "$file" - deviendra + # 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 +englober ses variables avec des `"` par défaut[^3] 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. Il y a potentiellement une infinité de situations +distinctes les une des autres dans lesquelles ce genre de situations +surviennent. Je ne vais en évoquer une seule autre, celle de l'utilisation de +`find`. + +Les globs que l'on a vu précédemment ne permettent pas, dans POSIX du moins, de +faire des recherches récursives. `./*.pdf` ne correspondra qu'aux fichiers du +*dossier courant* terminant par `.pdf`. Si l'on veut descendre dans les +sous-répertoires *et* avoir tout un tas d'autres filtres à notre disposition - +tel que la taille du fichier, sa date de dernière modification etc - il faut +avoir recours à `find`. `find` renvoie une liste des fichiers correspondant aux +filtres qu'on lui a indiqué. En admettant que l'on a un dossier dans lequel un +fichier `fichier 2.pdf se trouve` : + + $ find -name '*.pdf' + ./fichier.pdf + ./dossier/fichier 2.pdf + ./fichier 1.pdf + +On peut supposer qu'il sera possible d'itérer sur cette liste à l'aide d'une +boucle `for` pour effectuer une opération sur chacun de ces fichiers : + + $ for file in $(find -name '*.pdf');do + printf "on traite le fichier %s\n" "$file" + done + on traite le fichier ./fichier.pdf + on traite le fichier ./dossier/fichier + on traite le fichier 2.pdf + on traite le fichier ./fichier + on traite le fichier 1.pdf + +Mais patatra, *encore* notre souci de découpage. Le développement de la capture +de commande `$(find -name '*.pdf')` se fait avant le découpage. On a donc en +réalité exécuter : + + $ for file in ./fichier.pdf ./dossier/fichier 2.pdf ./fichier 1.pdf;do + ... + +Il y a [tout un tas](https://dwheeler.com/essays/filenames-in-shell.html) de +manière de contourner ce problème mais je vais en proposer une seule ici, celle +qui me semble la plus portable et flexible. + +Cette solution consiste à utiliser l'option `-print0` de `find` conjointement +avec l'option `-0` d'`xargs`. Cela permettra à `find` de délimiter les +différents fichiers trouvés par le caractère nul et à `xargs` de délimiter les +arguments sur ce même caractère nul. Cela résout notre problème puisque le seul +caractère qui ne peut *pas* être dans le nom d'un fichier est justement le +caractère NUL[^2]. Depuis 2023 POSIX inclus les options nécessaires dans `find` +et `xargs` permettant de le faire. Si l'on ne peut pas garantir que ce soit +implémenté partout du fait que la spécification est récente - et parce que tout +les outils UNIX ne font pas d'énormes efforts pour respecter POSIX - c'est tout +de même la solution que je privilégie. -J'aurais tendance à dire qu'étant donné la manière dont ils sont présentés, ce -qui constitue cette partie sont plutôt des *commandes* que des *scripts*. -Habituellement les *scripts* vont être une ou plusieurs commandes inscrites dans -un fichier que l'on éxecute comme une seule commande par la suite. C'est du -détail mais si une section au sujet de la "scriptisation" des commandes devait -être ajoutée ça rendrait les choses plus claires. +L'idée générale est de trouver les fichiers que l'on veut, par exemple tous les +fichiers terminant par `.pdf` (`find -type f -name '*.pdf'`) et de les passer à +`xargs` pour lancer une commande avec ces fichiers pour arguments : -### Convertir des formats d'image + $ find -type f -name '*.pdf' -print0 | xargs -0 ls -la + -rw-r--r-- 1 arthur arthur 0 15 mai 10:27 './dossier/fichier 2.pdf' + -rw-r--r-- 1 arthur arthur 0 15 mai 10:27 './fichier 1.pdf' + -rw-r--r-- 1 arthur arthur 0 15 mai 10:27 ./fichier.pdf -La commande `mogrify -format formatfinal *.formatàconvertir` a une -particularité qui n'est pas renseignée. Elle va convertir tous les fichier avec -l'extension `.formatàconvertir` dans le dossier courant. Ce comportement repose -sur l'utilisation des "globs" ou "shell patterns". Le caractère `*` veut dire -"n'importe quel caractère autant de fois que nécessaire". Donc -`*.formatàconvertir` veut dire "tous les fichiers se terminant par -`.formatàconvertir`. On peut penser les globs comme des expressions régulières -beaucoup moins puissantes. +On constate qu'il n'y a bien qu'aucun `ls` n'a été tenté sur un fichier +n'existant pas. Le découpage a été fait correctement. La commande `xargs` est +assez complexe et je ne vais pas plus en parler ici. A noter que dans sa version +GNU `xargs` va tout de même exécuter une fois la commande si aucun argument ne +lui a été fourni en entrée standard. Je *crois* que ce comportement n'est pas +POSIX. Pour reproduire le comportement plus intuitif d'aucune exécution si +`find` ne trouve aucun fichier il faut lui ajouter l'option `-r`. La version BSD +ne souffre pas du même problème. Pour le reste de cet article j'omet le `-r`. -D'autres caractères intéressants : +### Mauvaise gestion des cas où aucun fichier ne correspond au critère de recherche - * `?` : "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". +Aussi surprenant que cela puisse paraître[^5] lorsque le shell développe un glob +mais ne trouve aucun fichier correspondant le glob lui même sera renvoyé : -On peut donc étendre la commande présentée pour lui faire des choses plus -précises et alambiquées comme : + $ ls *.truc + ls: impossible d'accéder à '*.truc': Aucun fichier ou dossier de ce type + $ touch machin.truc + $ ls *.truc + machin.truc - mogrify -format formatfinal *-[!1][0-9][0-9]-??.formatàconvertir +C'est inoffensif pour un `ls` mais cela pourrait mener à l'exécution de +commandes que l'on ne souhaite pas. Lorsqu'on utilise les globs pour amorcer une +boucle comme vu précédemment la solution est de vérifier à chaque fois si le +fichier sur lequel on tente de lancer la commande existe bel et bien[^4] : -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 `.formatàconvertir`. + for file in ./*.pdf;do + if [ -e "$file" ];then + cmd "$file" + fi + done -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 +Que l'on peut aussi écrire : - ls ?.jpg -va s'étendre en + for file in ./*.pdf;do + [ -e "$file" ] && cmd "$file" + done - ls a.jpg b.jpg +Dans certains shells il est possible de modifier ce comportement. Par exemple +dans bash et zsh : -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. + shopt -s nullglob # Pour BASH + setopt NULL_GLOB # Pour ZSH + for file in ./*.pdf;do + cmd "$file" + done -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` : +Revient au même mais est plus efficace puisque l'on a plus à vérifier +l'existence du fichier à chaque itération de la boucle. - $ ls *.jpg - # devient - $ ls -azerty.jpg - ls : option invalide -- 'z' +### Manque de portabilité + +Le monde du shell est, encore en 2025, très morcelé. On a l'impression que +toutes les personnes faisant du shell partagent plus ou moins le même +environnement technique mais c'est lion d'être le cas. Le shell par défaut sur +les linux est traditionnellement `bash`. Sur MacOS c'est `zsh` depuis quelques +temps. Sur OpenBSD c'est `ksh`. Les scripts sont souvent écrits pour fonctionner +avec `/bin/sh` qui est généralement un lien symbolique vers un autre shell - par +exemple `dash` sous debian. Autant dire qu'il est difficile de garantir qu'un +script shell s'exécute correctement sur la plupart des machines. A ce sujet voir +[le portage de catium vers OpenBSD et MacOS](/portabilite/) ou [le portage de +catium vers debian 1.3](/catium-archeo/). + +Puisque ce zine est sur le web, qu'il s'adresse à une communauté ayant une +diversité de machine assez grande je pense qu'il est utile de proposer des +alternatives portables de chaque commande. Il n'y a pas une seule technique +magique permettant d'assurer la portabilité d'un script. L'idéal reste de le +tester sur les systèmes sur lesquels on veut qu'il fonctionne. + +Cela dit l'outil [shellcheck](https://www.shellcheck.net/) détecte tout un tas +de soucis dans les scripts shell dont [problèmes de +portabilité](https://github.com/koalaman/shellcheck?tab=readme-ov-file#portability). +De plus il existe un standard mi-descriptif mi-prescriptif nommé POSIX qui +spécifie, entre autre, le fonctionnement des [commandes traditionnelles du monde +Unix](https://pubs.opengroup.org/onlinepubs/9799919799/idx/utilities.html). Dans +l'ensemble se limiter à ces commandes et aux options spécifiées dans ce standard +est un bon moyen d'augmenter les chances qu'un script soit portable. Le shell +depuis lequel je teste mes scripts est `dash`. Il est, en théorie, fait pour +être conforme au standard POSIX mais dans la pratique ce n'est pas tout à fait +le cas. Qu'un script fonctionne sous `dash` ne garantit donc pas que sa syntaxe +soit strictement POSIX. + +Dans l'ensemble les règles de base vont être : + + * Utiliser le shebang `#!/bin/sh` en début de script + * Ne pas utiliser d'option non POSIX + * Ne pas utiliser de commande non POSIX autre que celles clairement déclarées + en dépendances. + * Vérifier que le script est correctement exécuté par `dash` + * Vérifier que `shellcheck` ne renvoit aucune erreur -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 `./` : +## Les scripts - $ ls ./-azerty.jpg - -azerty.jpg +J'aurais tendance à dire qu'étant donné la manière dont ils sont présentés, ce +qui constitue cette partie sont plutôt des *commandes* que des *scripts*. +Habituellement les *scripts* vont être une ou plusieurs commandes inscrites dans +un fichier que l'on éxecute comme une seule commande par la suite. C'est du +détail mais si une section au sujet de la "scriptisation" des commandes devait +être ajoutée ça rendrait les choses plus claires. -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 : +### Convertir des formats d'image - $ ls -larth -- *.jpg - # devient - $ ls -larth -- -azerty.jpg - -rw-r--r-- 1 arthur arthur 0 10 mai 11:27 -azerty.jpg +La version du zine : -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. + * ne gère pas correctement les fichiers commençant par un `-` -Des alternatives plus robustes de notre commande sont donc dans l'ordre de +Des alternatives plus robustes de cette commande sont, dans l'ordre de préférence : mogrify -format formatfinal ./*.formatàconvertir mogrify -format formatfinal -- *.formatàconvertir -Bien sûr l'utilisation de ce genre de commande est habituellement personnelle et -faite dans un environnement maitrisé. Si l'on sait exactement comment sont -nommés nos fichiers il n'y a pas besoin de rendre plus robuste ces commandes. Je -propose ces alternatives au cas-où vous vouliez les partager avec des personnes -dont vous ne connaissez pas le nommage des fichiers et pour apprendre des choses -chouettes à propos du shell :) +Une version recursive pourrait être : -### Diffusion d'erreur - -#### Modifier les images par lot + find . -name '*.formatàconvertir' -type f -print0 | xargs -0 mogrify -format formatfinal -La commande - for img in *.jpg; do - convert "$img" -colorspace Gray -dither FloydSteinberg -colors 4 "dither_$img"; - done - -contient la même faiblesse que la précédente. On peut la corriger simplement en -ajoutant `./` avant le caractère `*`. - -Cette commande peut poser un autre problème. Le shell, lorsqu'il développe le -glob mais ne trouve aucun fichier correspondant, va tout de même tenter de -lancer la commande avec le glob littéralement : +### Diffusion d'erreur - $ ls *.truc - ls: impossible d'accéder à '*.truc': Aucun fichier ou dossier de ce type - $ touch machin.truc - $ ls *.truc - machin.truc +#### Modifier une seule image -Donc dans notre commande s'il n'existe aucun fichier se terminant par `.jpg` -dans le dossier courant la commande `convert` va tout de même s'éxecuter une -fois avec `*.jpg` comme comme argument : +RAS - convert "*.jpg" -colorspace Gray -dither FloydSteinberg -colors 4 "dither_*.jpg"; +#### Modifier les images par lot -Ce qui n'est pas du tout ce que l'on voulait ! Dans le meilleur des cas cela -provoque une erreur un peu étrange à laquelle on s'attendait pas et dans le pire -des cas la commande fait sens pour le shell et opère sur des fichiers que l'on -ne voulait pas toucher. Malheureusement la solution à cela est un peu sale et -consiste à revérifier à l'intérieur de la boucle si `$img` contient le chemin -d'un fichier qui existe vraiment : +La version du zine : - if [ -e "$img" ];then - cmd - fi + * ne gère pas correctement les fichiers commençant par un `-` + * ne gère pas correctement l'absence de fichier correspondant au critère de + recherche Une version plus robuste et portable serait donc : @@ -421,35 +465,35 @@ Une version plus robuste et portable serait donc : fi done -Dans certains shells il est possible de modifier ce comportement. Par exemple -dans bash et zsh : - - shopt -s nullglob # Pour BASH - setopt NULL_GLOB # Pour ZSH - for img in ./*.jpg; do - convert "$img" -colorspace Gray -dither FloydSteinberg -colors 4 "dither_$img"; - done +Une version récursive pourrait être : -Revient au même mais est plus efficace puisque l'on a plus à vérifier -l'existence du fichier à chaque itération de la boucle. + find . -name '*.jpg' -type f -print0 | + xargs -0 -n1 sh -c 'convert "$1" -colorspace Gray -dither FloydSteinberg -colors 4 "dither_${1#./}"' -- ### Redimensionner les images pour le web -Même remarques que pour la commande précédente. +La version du zine : -### Seam Carving + * ne gère pas correctement les fichiers commençant par un `-` + * ne gère pas correctement l'absence de fichier correspondant au critère de + recherche -Maintenant que l'on sait ce que sont les globs on comprend pourquoi il faut -"échapper" le caractère `!` avec un `\` : +Une version plus robuste et portable serait donc : + + for img in ./*.jpg; do + if [ -e "$img" ];then + convert "$img" -resize 3000x2000 -strip -quality 86 "$img"; + fi + done - convert image.jpg -liquid-rescale 60x100%\! image_reduce.png +Une version récursive pourrait être : -En l'occurence ce n'est pas un souci pour cette commande mais c'est une bonne -idée de le faire de manière générale sans quoi `!` pourrait être interprété -comme un glob et non pas comme un spécificité d'image-magick. + find . -name '*.jpg' -type f -print0 | + xargs -0 -I{} convert "{}" -resize 3000x2000 -strip -quality 86 "{}" -Je ne sais pas si c'est voulu mais cette commande converti un jpeg en png. -Peut-être est-ce une faute de frappe ? +### Seam Carving + +RAS ### Convertir des fichiers .mov en .mp4 @@ -457,15 +501,29 @@ RAS ### Convertir des fichiers m4a en mp3 -Même remarques au sujet du glob dans la boucle for : +La version du zine : - for f in ./*.m4a - #plutôt que - for f in *.m4a + * ne gère pas correctement les fichiers commençant par un `-` + * ne gère pas correctement l'absence de fichier correspondant au critère de + recherche -le renommage se fait avec la syntaxe `"${f%.m4a}.mp3"`. En shell il est possible -d'invoquer la valeur d'une variable `f` en écrivant `$f` ou `${f}`. La syntaxe -avec les `{}` permet deux choses : +Une version plus robuste et portable serait donc : + + for vid in ./*.m4a; do + if [ -e "$vid" ];then + ffmpeg -i "$vid" -codec:v copy -codec:a libmp3lame -q:a 2 "${vid%.m4a}.mp3" + fi + done + +Une version récursive pourrait être : + + find . -name '*.m4a' -type f -print0 | + xargs -0 -n1 sh -c 'ffmpeg -i "$1" -codec:v copy -codec:a libmp3lame + -q:a 2 "${1%.m4a}.mp3"' -- + +Au passage petite explication du renommage. Il se fait avec la syntaxe +`"${f%.m4a}.mp3"`. En shell il est possible d'invoquer la valeur d'une variable +`f` en écrivant `$f` ou `${f}`. La syntaxe avec les `{}` permet deux choses : * coller la valeur de la variable à une chaîne de caractère. En faisant `echo "$ftruc"` le shell n'aurait pas la capacité de savoir que l'on veut ce qu'il @@ -481,22 +539,23 @@ avec les `{}` permet deux choses : par exemple `truc.m4a`, lui retirer sno extension pour avoir `truc` et y ajouter une nouvelle extension `.mp3` pour obtenir `truc.mp3` en fin de course. -### Supprimer les espaces dans les noms de fichier +### Exporter chaque glyphe d’une fonte en fichier svg -La commande `for f in *\ *; do mv "$f" "${f// /_}";` comporte plusieurs erreurs -ou faiblesses. +C'est du python. -Premièrement il manque un `done` à la fin sans quoi on reste dans la boucle -`for` et le nous demande une commande supplémentaire. +### Supprimer les espaces dans les noms de fichier -Deuxièmement la désormais habituelle remarque au sujet de la protection contre -les fichiers commençant pas `-`. +La version du zine : -Troisièmement elle utilise la syntaxe `${f// /_}` qui n'est pas POSIX. Pour une -version qui devrait fonctionner partout il va falloir être un peu plus verbeux : + * ne gère pas correctement les fichiers commençant par un `-` + * ne gère pas correctement l'absence de fichier correspondant au critère de + recherche + * n'est pas portable + +Une version plus robuste et portable serait donc : for f in ./*\ *;do - mv "$f" "$(echo "$f" | tr ' ' '_')" + [ -e "$f" ] && mv "$f" "$(echo "$f" | tr ' ' '_')" done La *capture de commande* introduite avec `$()` permet d'exécuter une commande @@ -506,23 +565,17 @@ par des `_`. Ainsi une fois la capture de commande évaluée la commande que l'o exécute vraiment ressemblera à : for f in ./*\ *;do - mv "test truc" "test_truc" + [ -e "./test truc" ] && mv "./test truc" "./test_truc" done C'est pas terriblement performant parce qu'il faut, pour chaque fichier ou dossier, exécuter une sous commande pour avoir une version sans les espaces. Cela dit la version bash est pas immensément plus rapide. -Finalement, s'il n'y a pas de fichier avec un espace on retombe sur le souci vu -précedemment où l'on va tenter un `mv` sur le glob lui-même. La version POSIX -qui ne se trompe pas si un fichier commence avec un `-`, qui ne renvoie pas -d'erreur s'il n'existe pas de fichier sans espace est : +Une version récursive pourrait être : - for f in ./*\ *;do - [ -e "$f" ] && mv "$f" "$(echo "$f" | tr ' ' '_')" - done - -On pourrait imaginer d'autres solutions mais on reviendra dessus plus tard. + find . -name '* *' -print0 | + xargs -0 -n1 sh -c 'mv "$1" "$(echo "$1" | tr " " "_")"' -- Si l'on accepte une dépendance on peut utiliser le très bon `rename`, installable sur debian en faisant `apt install rename`. Il permet de renommer @@ -538,12 +591,14 @@ suivra dans la partie au sujet de `find` plus tard. ### Renommer des fichiers par lot -`let` n'est pas POSIX. Ce script ne fonctionnera pas avec tous les shell. En -plus de faire attention au glob je propose d'utiliser une expression -arithmétique. Le shell POSIX spécifié pour être capable de faire des maths -rudimentaire. Par ailleurs l'option `-v` de `mv` n'est pas POSIX non plus Il va -falloir la reproduire "à la main". Je renomme les variables pour un peu plus de -clarté : +La version du zine : + + * ne gère pas correctement les fichiers commençant par un `-` + * ne gère pas correctement l'absence de fichier correspondant au critère de + recherche + * n'est pas portable + +Une version plus robuste et portable serait donc : for file in ./*.jpg; do i=$(( $i + 1 )) @@ -552,8 +607,12 @@ clarté : done Les raisons d'utiliser `printf` plutôt qu'`echo` sont nombreuses et documentées -(ici)[https://unix.stackexchange.com/questions/65803/why-is-printf-better-than-echo) -par le goat Stéphane Chazelas. +[ici](https://unix.stackexchange.com/questions/65803/why-is-printf-better-than-echo) +par Stéphane Chazelas. `let` et l'option `-v` de `mv` ne sont pas POSIX. + +Une version récursive pourrait être : + + Reste à trouver ### Compresser un PDF @@ -561,104 +620,43 @@ RAS ### Lancer une impression automatiquement -Le script proposé comporte plusieurs faiblesses. - -Premièrement il est possible de le rendre un peu plus robuste en utilisant -l'option POSIX `-p` de `mkdir`. Elle permettra de faire fonctionner le `mkdir` -même si l'on a décidé d'ajouter plus de profondeur aux variables `archivebox` et -`printinbox` ou si les dossiers existent déjà. On sait jamais. - -Deuxièmement les boucles `for` bouclant sur la sortie d'une commande `find` sont -sujet à pleins de soucis, notamment la gestion des espaces. Si l'on a un pdf -nommé `rapport (1).pdf` la boucle for va itérer une fois avec le "fichier" -`rapport` et une autre fois avec le rapport `(1).pdf`. Cela est du au fait que -le retour de la commande find ne "protège" pas les noms des fichiers comme on -l'aurait fait à la main avec des échappements ou des guillements `"`. Ainsi la -boucle for deviendra : - - for step in rapport (1).pdf - -Le shell découpera les éléments après le `in` sur les espaces et considèrera que -`rapport` et `(1).pdf` sont deux fichiers différents. La raison pour laquelle ce -problème n'est pas arrivé plus tôt est que les globs sont étendus *après* le -découpage des éléments. Cela évite de découper des fichiers contenant des -espaces en plusieurs fichiers. Ici étant donné la situation je pense que -l'utilisation du glob est appropriée : - - for file in "./$printinbox/*.pdf";do - ... - done - -On verra en bonus après quand il est utile d'utiliser `find` et toutes les -limites qui y sont associées. - -A l'intérieur de la boucle `for` on remplace l'option `-v` et on englobe les -variables de guillemets pour à nouveau éviter qu'elles ne soient interprétées -comme plusieurs éléments séparés. Dans l'ensemble, à moins que vous ne vouliez -spécifiquement que le contenu d'une variable soit étendu en plusieurs éléments, -vous devriez englober les variables avec des guillemets. Ce cas représente -possiblement 100% des cas notamment lorsque l'on débute en shell. - -Notre boucle `for` est alors : - - for file in ./$printinbox/*.pdf;do - if [ -e "$file" ];then - lpr -P "$printer" -o media=A4 -o fit-to-page "$file" - printf "On déplace %s dans %s" "$file" "$archivebox" - mv "$file" "$archivebox" - fi - done - -La deuxième boucle `for` utilise une syntaxe spécifique à `bash`. En version -POSIX cela serait : +La version du zine : - for i in 10 9 8 7 6 5 4 3 2 1;do - printf " \r" - printf "next try in %s s\r" "$i" - sleep 1 - done + * ne gère pas correctement les fichiers commençant par un `-` + * ne gère pas correctment les espaces dans les noms des fichiers + * n'est pas portable -Sans commentaire une version plus robuste et POSIX du script serait donc : +Une version plus robuste et portable serait donc : #! /bin/sh printer=Canon_LBP7100C_7110C archivebox="archivebox/" printinbox="printbox/" + interval="20" mkdir -p "$archivebox" "$printinbox" while true; do - - for file in ./$printinbox*.pdf ;do + for file in ./$printinbox*.pdf ;do if [ -e "$file" ];then printf "on copie %s dans %s\n" "$file" "$archivebox" mv "$file" "$archivebox" # copy in outbox (archives) fi - done - - # wait - for i in 10 9 8 7 6 5 4 3 2 1; do - printf " \r" - printf "next try in $i s \r" - sleep 1 - done + done + + i="$interval" + while [ "$i" -gt "0" ];do + printf " \r" + printf "next try in $i s \r" + sleep 1 + i=$(( $i - 1 )) + done done -Si l'on voulait vraiment rendre "configurable" le décompte on peut utiliser une -boucle `while` : - - interval="20" - - [...] +Une version récursive pourrait être : - i="$interval" - while [ "$i" -gt "0" ];do - printf " \r" - printf "next try in $i s \r" - sleep 1 - i=$(( $i - 1 )) - done + Reste à trouver ## Travailler avec des fichiers cachés @@ -680,44 +678,6 @@ Pour inclure les fichiers cachés il faut ajouter d'autres globs : ./fichier_pas_secret ./.fichier_secret -## Rendre tout récursif - -Une constante dans toutes ces commandes est qu'elles ne fonctionnent que pour -les fichiers se trouvant dans le dossier courant. Il est fréquent que l'on ait -besoin d'appliquer quelque chose pour tous les fichiers du dossier courant *et* -ses sous-dossiers. Dans ce cas-ci il est utile d'avoir recours à la commande -`find`. `find` permet aussi de filtrer de manière beaucoup plus fine que les -globs, sur la taille des fichiers, des regex dans leurs noms, leur date de -dernière modif etc. - -Malheureusement, comme décrit précédemment, elle est vulnérable aux -fichiers avec des espaces dans le nom. Il existe plusieurs stratégies pour -contourner ce problème. - -### Séparer les chemins par le caractère NUL - -Cette solution résout notre problème puisque le seul caractère qui ne peut *pas* -être dans le nom d'un fichier est justement le caractère NUL[^2]. Depuis 2023 -POSIX inclus les options nécessaires dans `find` et `xargs` permettant de le -faire. Si l'on ne peut pas garantir que ce soit implémenté partout du fait que -la spécification est récente - et parce que tout les outils UNIX ne font pas -d'énormes efforts pour respecter POSIX - c'est tout de même la solution que je -privilégie. - -L'idée générale est de trouver les fichiers que l'on veut, par exemple tous les -fichiers terminant par `.pdf` (`find -type f -name '*.pdf'`) et de les passer à -`xargs` pour lancer une commande avec ces fichiers pour arguments : - - $ find -type f -name '*.pdf' -print0 | xargs -0 echo - ./rapport (1).pdf - ./rapport.pdf - -Par exemple si l'on devait adapter la toute première commande pour être -récursive et ne s'appliquer que sur les fichiers de plus de 500Ko : - - $ find -type f -name '*.formatàconvertir' -size +500k -print0 | - xargs -0 mogrify -format formatfinal - ## Faire des scripts Expliquer comment faire des scripts de tout ça et comment les "installer". @@ -731,3 +691,7 @@ rarement les commandes/scripts comme des logiciels à proprement parler. [^1]: https://mastodon.design/@timotheegoguely/114478679053770184 [^2]: Oui, le caractère de retour à la ligne peut exister dans le nom d'un fichier +[^3]: https://unix.stackexchange.com/questions/171346/security-implications-of-forgetting-to-quote-a-variable-in-bash-posix-shells/171347#171347 +[^4]: retrouver le texte de Cahzelas qui explique que `! -e` ne garantit par que + le fichier n'existe pas 🤯 +[^5]: https://unix.stackexchange.com/questions/204803/why-is-nullglob-not-default/204944#204944