arthur.bebou

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

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

Log | Files | Refs |

index.sh (10660B)


      1 #! page
      2 title: Un commentaire à propos de \"Avoid Software\"
      3 author: Arthur Pons
      4 description: Avoid software ? Embrace shell scripts
      5 publication: 2025-05-15
      6 
      7 section: main
      8 
      9 ## Introduction
     10 
     11 [Timothée](https://timothee.goguely.com/) a récemment fait un cours avec des
     12 airs de [missing semester](https://missing.csail.mit.edu/) pour parler de JS,
     13 de CLI et de vie professionnelle en tant que graphiste[^1]. A cette occasion là
     14 il a trouvé une ressource parlant de CLI, faite par et pour des graphistes,
     15 nommée [Avoid Software](https://avoidsoftware.sarahgarcin.com/index.html).
     16 
     17 Cet article a pour but d'être une contribution à ce fanzine :
     18 
     19   * en apportant des précisions sur le fonctionnement des scripts
     20   * en proposant des alternatives aux scripts lorsqu'
     21     * ils ne sont pas POSIX
     22     * ils ne fonctionnent pas sur les fichiers contenant des espaces
     23   * garantissant que tout fonctionne sous OpenBSD
     24   * en proposant une partie expliquant comment transformer les commandes en
     25     scripts qui peuvent prendre des arguments
     26 
     27 ## Les dépendances
     28 
     29 ## Les scripts
     30 
     31 J'aurais tendance à dire qu'étant donné la manière dont ils sont présentés, ce
     32 qui constitue cette partie sont plutôt des *commandes* que des *scripts*.
     33 Habituellement les *scripts* vont être une ou plusieurs commandes inscrites dans
     34 un fichier que l'on éxecute comme une seule commande par la suite. C'est du
     35 détail mais si une section au sujet de la "scriptisation" des commandes devait
     36 être ajoutée ça rendrait les choses plus claires.
     37 
     38 ### Convertir des formats d'image
     39 
     40 La commande `mogrify -format formatfinal *.formatàconvertir` a une
     41 particularité qui n'est pas renseignée. Elle va convertir tous les fichier avec
     42 l'extension `.formatàconvertir` dans le dossier courant. Ce comportement repose
     43 sur l'utilisation des "globs" ou "shell patterns". Le caractère `*` veut dire
     44 "n'importe quel caractère autant de fois que nécessaire". Donc
     45 `*.formatàconvertir` veut dire "tous les fichiers se terminant par
     46 `.formatàconvertir`. On peut penser les globs comme des expressions régulières
     47 beaucoup moins puissantes.
     48 
     49 D'autres caractères intéressants :
     50 
     51   * `?` : "n'importe quel caractère une fois"
     52   * `[]` : introduit ce que l'on appelle une "classe de caractère". En écrivant
     53     `[abc]` on dit "une fois le caractère a, b ou c". Il est également possible
     54     d'utiliser le caractère `-` pour décrire une étendue de caractère. `[3-8]`
     55     ou `[d-h]` voudront dire "une fois un entier entre 3 et 8 inclus" et "une
     56     fois une lettre minuscule entre d et h dans l'ordre alphabétique inclus".
     57   * `!` : dans une classe de caractère permet de prendre le complément des
     58     caractères. `[!abc]` veut dire "une fois n'importe quel caractère *sauf* a,
     59     b ou c".
     60 
     61 On peut donc étendre la commande présentée pour lui faire des choses plus
     62 précises et alambiquées comme :
     63 
     64     mogrify -format formatfinal *-[!1][0-9][0-9]-??.formatàconvertir
     65 
     66 convertir tous les fichiers commençant n'importe comment, suivi d'un tiret,
     67 suivi de n'importe quel caractère n'étant pas un `1` suivi de deux entiers suivi
     68 d'un tiret suivi d'exactement deux caractères suivi de `.formatàconvertir`.
     69 
     70 En arrière plan le shell "développe" le glob en le remplaçant par tous les
     71 chemins auxquels il correspond. Si l'on a trois fichiers dans notre dossier
     72 `a.jpg`, `b.jpg` et `machin.jpg` la commande
     73 
     74     ls ?.jpg
     75 
     76 va s'étendre en
     77 
     78     ls a.jpg b.jpg
     79 
     80 avant de s'éxecuter. Il est possible dans certains shell à l'écriture de la
     81 commande d'obtenir un retour des fichiers qui correspondent en appuyant sur la
     82 touche tabulation comme si l'on voulait compléter le glob. Avec ma configuration
     83 de `zsh` le développement se fait carrément en direct dans le prompt.
     84 
     85 Si vous êtes particulièrement alerte ce fonctionnement par réécriture de
     86 commande devrait vous donner envie de tester un truc. Que se passe-t-il si un
     87 le nom d'un fichier commence par `-` ? Admettons qu'il existe un fichier nommé
     88 `-azerty.jpg` et que l'on utilise le glob `*.jpg` en argument de la fonction
     89 `ls` :
     90 
     91     $ ls *.jpg
     92     # devient
     93     $ ls -azerty.jpg
     94     ls : option invalide -- 'z'
     95 
     96 On se retrouve avec une erreur. Et pour cause, la commande a cru que
     97 `-azerty.jpg` est la déclaration d'options de la commande `ls`. C'est d'autant
     98 plus facile de tomber sur cette erreur que le tiret `-` arrive avant la plupart
     99 des autres caractères dans l'ordre alphanumérique.
    100 
    101 Il y a deux manières de se prémunir de cette erreur. La manière la plus
    102 universelle est d'ajouter `./` devant le glob. `./` voulant dire "le dossier
    103 courant" le glob se développera sur exactement les mêmes fichier qu'auparavant
    104 mais les chemins démarreront tous `./` :
    105 
    106     $ ls ./-azerty.jpg
    107     -azerty.jpg
    108 
    109 Il n'y a donc plus d'ambiguité entre les options et les noms des fichiers. Une
    110 deuxième solution est d'ajouter `--` entre les options et les arguments de la
    111 commande :
    112 
    113     $ ls -larth -- *.jpg
    114     # devient
    115     $ ls -larth -- -azerty.jpg
    116     -rw-r--r-- 1 arthur arthur 0 10 mai   11:27  -azerty.jpg
    117 
    118 Ici `--` permet à la commande de savoir que tout ce qu'il suit doit être
    119 interpréter comme des arguments et non pas comme de potentielles options. Bien
    120 que [cette syntaxe soit
    121 POSIX](https://pubs.opengroup.org/onlinepubs/9699919799/basedefs/V1_chap12.html#tag_12_02)
    122 toutes les commandes ne respectent pas ce principe, en particulier les commandes
    123 qui ne sont pas "de base" sur les systèmes Unix. La première solution est donc
    124 préférable.
    125 
    126 Des alternatives plus robustes de notre commande sont donc dans l'ordre de
    127 préférence :
    128 
    129     mogrify -format formatfinal ./*.formatàconvertir
    130     mogrify -format formatfinal -- *.formatàconvertir
    131 
    132 Bien sûr l'utilisation de ce genre de commande est habituellement personnelle et
    133 faite dans un environnement maitrisé. Si l'on sait exactement comment sont
    134 nommés nos fichiers il n'y a pas besoin de rendre plus robuste ces commandes. Je
    135 propose ces alternatives au cas-où vous vouliez les partager avec des personnes
    136 dont vous ne connaissez pas le nommage des fichiers et pour apprendre des choses
    137 chouettes à propos du shell :)
    138 
    139 ### Diffusion d'erreur
    140 
    141 #### Modifier les images par lot
    142 
    143 La commande
    144 
    145     for img in *.jpg; do
    146         convert "$img" -colorspace Gray -dither FloydSteinberg -colors 4 "dither_$img";
    147     done
    148 
    149 contient la même faiblesse que la précédente. On peut la corriger simplement en
    150 ajoutant `./` avant le caractère `*`.
    151 
    152 Cette commande peut poser un autre problème. Le shell, lorsqu'il développe le
    153 glob mais ne trouve aucun fichier correspondant, va tout de même tenter de
    154 lancer la commande avec le glob littéralement :
    155 
    156     $ ls *.truc
    157     ls: impossible d'accéder à '*.truc': Aucun fichier ou dossier de ce type
    158     $ touch machin.truc
    159     $ ls *.truc
    160     machin.truc
    161 
    162 Donc dans notre commande s'il n'existe aucun fichier se terminant par `.jpg`
    163 dans le dossier courant la commande `convert` va tout de même s'éxecuter une
    164 fois avec `*.jpg` comme comme argument :
    165 
    166     convert "*.jpg" -colorspace Gray -dither FloydSteinberg -colors 4 "dither_*.jpg";
    167 
    168 Ce qui n'est pas du tout ce que l'on voulait ! Dans le meilleur des cas cela
    169 provoque une erreur un peu étrange à laquelle on s'attendait pas et dans le pire
    170 des cas la commande fait sens pour le shell et opère sur des fichiers que l'on
    171 ne voulait pas toucher. Malheureusement la solution à cela est un peu sale et
    172 consiste à revérifier à l'intérieur de la boucle si `$img` contient le chemin
    173 d'un fichier qui existe vraiment :
    174 
    175     if [ -e "$img" ];then
    176         cmd
    177     fi
    178 
    179 Une version plus robuste et portable serait donc :
    180 
    181     for img in ./*.jpg; do
    182         if [ -e "$img" ];then
    183             convert "$img" -colorspace Gray -dither FloydSteinberg -colors 4 "dither_$img";
    184         fi
    185     done
    186 
    187 Dans certains shells il est possible de modifier ce comportement. Par exemple
    188 dans bash et zsh :
    189 
    190     shopt -s nullglob # Pour BASH
    191     setopt NULL_GLOB  # Pour ZSH
    192     for img in ./*.jpg; do
    193             convert "$img" -colorspace Gray -dither FloydSteinberg -colors 4 "dither_$img";
    194     done
    195 
    196 Revient au même mais est plus efficace puisque l'on a plus à vérifier
    197 l'existence du fichier à chaque itération de la boucle.
    198 
    199 ### Redimensionner les images pour le web
    200 
    201 Même remarques que pour la commande précédente.
    202 
    203 ### Seam Carving
    204 
    205 Maintenant que l'on sait ce que sont les globs on comprend pourquoi il faut
    206 "échapper" le caractère `!` avec un `\` :
    207 
    208     convert image.jpg -liquid-rescale 60x100%\! image_reduce.png
    209 
    210 En l'occurence ce n'est pas un souci pour cette commande mais c'est une bonne
    211 idée de le faire de manière générale sans quoi `!` pourrait être interprété
    212 comme un glob et non pas comme un spécificité d'image-magick.
    213 
    214 Je ne sais pas si c'est voulu mais cette commande converti un jpeg en png.
    215 Peut-être est-ce une faute de frappe ?
    216 
    217 ### Convertir des fichiers .mov en .mp4
    218 
    219 RAS
    220 
    221 ### Convertir des fichiers m4a en mp3
    222 
    223 Même remarques au sujet du glob dans la boucle for :
    224 
    225     for f in ./*.m4a
    226     #plutôt que
    227     for f in *.m4a
    228 
    229 le renommage se fait avec la syntaxe `"${f%.m4a}.mp3"`. En shell il est possible
    230 d'invoquer la valeur d'une variable `f` en écrivant `$f` ou `${f}`. La syntaxe
    231 avec les `{}` permet deux choses :
    232 
    233   * coller la valeur de la variable à une chaîne de caractère. En faisant `echo
    234     "$ftruc"` le shell n'aurait pas la capacité de savoir que l'on veut ce qu'il
    235     y a dans la variable `f` suivi de `truc`. Il penserait que l'on veut la
    236     variable `ftruc`. La solution est d'écrire `echo "${f}truc"`.
    237   * le retrait de suffixe et préfixe. `${f%.m4a}` renvoie la valeur de la
    238     variable `f` avec le plus petit suffixe `.m4a` retiré. Autrement dit c'est
    239     un moyen de retirer l'extension `.m4a` d'un nom de fichier. Il existe des
    240     syntaxes similaires pour retirer le plus grand suffixe possible et les
    241     préfixes les plus petits et grands.
    242 
    243 `${f%.m4a}.mp3` est donc un moyen de prendre une variable `$f` dans laquelle on a
    244 par exemple `truc.m4a`, lui retirer sno extension pour avoir `truc` et y ajouter
    245 une nouvelle extension `.mp3` pour obtenir `truc.mp3` en fin de course.
    246 
    247 ### Supprimer les espaces dans les noms de fichier
    248 
    249 La commande `for f in *\ *; do mv "$f" "${f// /_}";` comporte plusieurs erreurs
    250 ou faiblesses.
    251 
    252 Premièrement il manque un `done` à la fin sans quoi on reste dans la boucle
    253 `for` et le nous demande une commande supplémentaire.
    254 
    255 Deuxièmement la désormais habituelle remarque au sujet de la protection contre
    256 les fichiers commençant pas `-`.
    257 
    258 ## Au sujet du nom
    259 
    260 [^1]: https://mastodon.design/@timotheegoguely/114478679053770184