Le site arthur.bebou.netlib.re - retour accueil
git clone git://bebou.netlib.re/arthur.bebou
Log | Files | Refs |
commit 4e35bece86990586776fe03ee3e31af2cf459a70 parent 68dbf94c56f1a2f0bbb3a3717b00df51c891ec27 Auteurice: Arthur Pons <arthur.pons@unistra.fr> Date: Wed, 14 May 2025 17:20:03 +0200 On continuer avoid software On publie alors que pas fini mais pas grave Diffstat:
M | contents/avoid-software-commentaires/index.sh | | | 246 | ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++- |
1 file changed, 245 insertions(+), 1 deletion(-)
diff --git a/contents/avoid-software-commentaires/index.sh b/contents/avoid-software-commentaires/index.sh @@ -2,10 +2,18 @@ title: Un commentaire à propos de \"Avoid Software\" author: Arthur Pons description: Avoid software ? Embrace shell scripts -publication: 2025-05-15 +publication: 2025-05-14 section: main +Article non relu et **pas fini**. + +La plupart des informations intéressantes de cet article sont directement tirées +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)). + ## Introduction [Timothée](https://timothee.goguely.com/) a récemment fait un cours avec des @@ -255,6 +263,242 @@ Premièrement il manque un `done` à la fin sans quoi on reste dans la boucle Deuxièmement la désormais habituelle remarque au sujet de la protection contre les fichiers commençant pas `-`. +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 : + + for f in ./*\ *;do + mv "$f" "$(echo "$f" | tr ' ' '_')" + done + +La *capture de commande* introduite avec `$()` permet d'exécuter une commande +dans notre commande. Elle sera évaluée d'abord. En l'occurence elle émet le +contenu de `$f` dans la commande `tr ' ' '_'` qui va convertir tous les espaces +par des `_`. Ainsi une fois la capture de commande évaluée la commande que l'on +exécute vraiment ressemblera à : + + for f in ./*\ *;do + 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 : + + for f in ./*\ *;do + [ -e "$f" ] && mv "$f" "$(echo "$f" | tr ' ' '_')" + done + +On pourrait imaginer d'autres solutions mais on reviendra dessus plus tard. + +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 +des fichiers via l'utilisation de commandes perl, qu'on peut, ici seulement et +pour faire simple, considérer comme des commandes `sed`. Notre besoin est donc +satisfait avec : + + $ rename 'y/ /_/' ./* + +C'est super rapide et propre. En mode interactif je recommande l'utilisation de +`rename`. Dans des scripts je recommande la version précédente ou l'une qui +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é : + + for file in ./*.jpg; do + i=$(( $i + 1 )) + printf "renommé '%s' -> '%s'\n" "$file" "image_$i.jpg" + mv "$file" "image_$i.jpg" + 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. + +### Compresser un PDF + +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 : + + 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 + +Sans commentaire une version plus robuste et POSIX du script serait donc : + + #! /bin/sh + + printer=Canon_LBP7100C_7110C + archivebox="archivebox/" + printinbox="printbox/" + + mkdir -p "$archivebox" "$printinbox" + + while true; 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 + +Si l'on voulait vraiment rendre "configurable" le décompte on peut utiliser une +boucle `while` : + + interval="20" + + [...] + + i="$interval" + while [ "$i" -gt "0" ];do + printf " \r" + printf "next try in $i s \r" + sleep 1 + i=$(( $i - 1 )) + done + +## Travailler avec des fichiers cachés + +Si l'on a des fichiers cachés les globs type `./*.pdf` ne vont pas fonctionner. +Le glob `*` veut certes dire "n'importe quel caractère autant de fois que +nécessaire" *sauf* pour le `.` qui est spécial et, en début de fichier, dénote +les fichiers "cachés" : + + $ ls -a + . .. fichier_pas_secret .fichier_secret + $ for file in ./*;do printf "%s\n" "$file"; done + ./fichier_pas_secret + +Pour inclure les fichiers cachés il faut ajouter d'autres globs : + + $ for file in ./* ./.[\!.]* ./..?* ; do + [ -e "$file" ] && printf "%s\n" "$file" + done + ./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". + ## Au sujet du nom +Juste une remarque au sujet du fait que l'on évite pas de logiciel mais on +substitut du logiciel. Il est intéressant de se demander pourquoi on considère +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