Conctructions « with » en langage… bash !

Magazine
Marque
GNU/Linux Magazine
Numéro
204
Mois de parution
mai 2017
Domaines


Résumé
Dis-moi petit syntax checker, qu’as-tu à me dire sur mon code source ? Comment ça, tu me rends la main tout de suite ? Tu n’as rien trouvé ?? Pas le moindre petit warning ??? Allez, ne sois pas timide, je t’ai mis l’option ultra-verbose je te signale !! T’es pas censé être timide !! Écoute, tu vois bien que je travaille seul là-dessus... si tu ne me dis rien, qui d’autre va me faire la conversation ?? Bon. Tu l’auras voulu. Voyons si tu resteras de marbre quand j’aurai ajouté cette structure exotique à mon code source...

Body

Quelques mois plus tôt…

En ce moment je travaille sur debootstick, un outil pour générer des systèmes bootables. Mon but final est de pouvoir enchaîner les commandes suivantes sur un système Debian (ou Ubuntu...) :

$ debootstrap jessie os_tree

$ debootstick os_tree usb.img

L’outil debootstrap (à ne pas confondre avec debootstick donc !) utilisé en première ligne est un grand classique des systèmes Debian. Il permet de générer dans le répertoire donné en paramètre (ici os_tree) une arborescence minimale d’OS Debian (ici en version jessie). Mon outil debootstick se chargera alors, dans un deuxième temps, de convertir cette arborescence en image bootable (usb.img dans cet exemple).

On pourra ensuite écrire l’image bootable obtenue sur une clé USB (ici /dev/sdb) :

$ dd bs=10M if=usb.img of=/dev/sdb

Et l’OS pourra alors être démarré sur n’importe quelle machine.

Vous vous dites peut-être qu’on pourrait simplifier la chose, côté utilisateur, en intégrant dans debootstick l’appel préliminaire à la commande debootstrap. C’est d’ailleurs ce que font, en général, les outils de ce genre. De mon côté, j’ai préféré suivre la philosophie UNIX et restreindre l’outil à une seule fonction « atomique ». Cela rendra l’outil plus souple et, paradoxalement, plus puissant. En effet, on peut imaginer toute une gamme d’autres scénarios pour générer ou customiser notre arborescence os_tree. Par exemple :

$ docker run --name jessie-container -it debian:jessie /bin/sh

[docker-shell]$ [... cutomize ...]

[docker-shell]$ exit
$ mkdiros_tree

$ docker export jessie-container |tar xf - -C os_tree

$ debootstick os_tree usb.img

Et voilà. Après transfert sur une clé USB, on peut faire booter sur une machine physique ce qui n’était jusqu’à présent qu’un conteneur virtuel. Sympa, non ?

Le souci, c’est que pour l’instant cet outil n’existe que dans mon imagination. J’en suis dans les premières phases de développement, et je n’avance pas bien vite. Ce que je vous ai présenté comme une simple « conversion » cache en réalité une certaine complexité : il faut créer un fichier image, le partitionner, demander au noyau de créer des devices virtuels pour manipuler ces partitions, initialiser les systèmes de fichiers, faire des mount et des chroot, copier l’arborescence, installer les éventuels paquets manquants (noyau linux, bootloader, etc.), installer les bootloaders (BIOS et UEFI), puis tout redéfaire. Et je simplifie pas mal de choses (par exemple, pour obtenir une image de taille minimale, il faut ruser). Le plus ennuyeux, c’est quand on rencontre une erreur au beau milieu de l’exécution. Le cas typique, c’est le « no space left on device ». Le script s’arrête, et il laisse votre machine dans un état imprévisible, avec des montages sur un fichier temporaire qui n’existe plus, etc. Si on essaie de faire le ménage à la main, on y passe un bon quart d’heure. Jusque-là, j’optais donc pour la méthode Windows : je rebootais la machine. Mais je perds clairement trop de temps.

Pour résoudre ce souci, il faudrait que debootstick enregistre cet empilement de commandes délicates. En cas d’imprévu, il serait alors capable de tout défaire, en dépilant. S’il était écrit en Python par exemple, on pourrait utiliser des blocs with imbriqués. Mais debootstick est clairement orienté « système », donc le shell est plus approprié. Et en shell, pas de bloc with !

Pardon ? On me dit dans l’oreillette que vous ne seriez pas contre un petit rappel sur ce fameux bloc with. Faisons donc le point sur cette construction, telle qu’elle apparaît en Python.

1. Bloc « with » en Python

Prenons pour exemple la maxime : « Avec ce marteau, tous les problèmes ressemblent à des clous » :

problemes =['clou1','clou2']

with prendre_marteau()as marteau:

   for probleme in problemes:

      marteau.clouer(probleme)

On voit déjà que le bloc with met en évidence l’outil (ici le marteau), ainsi que le périmètre de son utilisation, le tout de manière assez élégante. Mais allons un peu plus loin. En fait, il y a du code caché qui s’exécute à l’entrée et à la sortie du bloc. Complétons notre exemple :

#!/usr/bin/env python

# -*- coding: utf8 -*-

importsys


class Marteau(object):

   def __enter__(self):

      print('marteau en main')

      returnself

   def __exit__(self,type, value, tb):

      print('marteau rangé')

   def clouer(self, objet):

      assert(objet.startswith('clou')), \

         "Je ne peux pas clouer '%s'!"%objet

      print(objet +' cloué.')


def prendre_marteau():

   return Marteau()


problemes =sys.argv[1:]

with prendre_marteau() as marteau:

   for probleme in problemes:

      marteau.clouer(probleme)
print('Au revoir!')

Voilà ce que ça donne à l’exécution :

$ ./test.py clou1 clou2

marteau en main

clou1 cloué.

clou2 cloué.

marteau rangé

Au revoir!

$

Et voici quelques explications.

1) Quand l’interpréteur Python a rencontré notre bloc with, il exécute prendre_marteau().

2) S’agissant d’un bloc with, le résultat de prendre_marteau() doit être un objet qui implémente les méthodes spéciales __enter__() et __exit__(). On appelle cet objet un context manager. L’interpréteur appelle alors la méthode __enter__() du context manager.

3) La méthode __enter__() renvoie un résultat, ici self (c’est souvent le cas), et ce résultat est assigné à la variable spécifiée après l’instruction as (ici marteau).

4) L’interpréteur exécute le contenu du bloc with.

5) En sortie du bloc, l’interpréteur appelle la méthode __exit__() du context manager.

6) L’exécution reprend après le bloc with.

Et que se passe-t-il si on essaie de clouer autre chose que des clous ? Essayons :

$ ./test.py clou1 vis clou2

marteau en main

clou1 cloué.

marteau rangé

Traceback (most recent call last):

File [...]

AssertionError: Je ne peux pas clouer 'vis'!

$

Pas de problème pour clouer clou1, en revanche la même tentative sur vis a levé une exception. Cette exception a visiblement interrompu la suite du bloc (puisque clou2 n’a pas non plus été cloué), et a provoqué une sortie prématurée du programme (puisque le message « Au revoir! » n’apparaît pas).

En revanche, on voit bien le message « marteau rangé », ce qui prouve que la fonction __exit__() a bien été appelée, pour faire le ménage en sortie du bloc with. Et ceci malgré l’exception !!

Les connaisseurs me diront qu’on pourrait obtenir le même genre de comportement en utilisant la clause finally d’un bloc try. En fait, la sémantique est quand même un peu différente, car avec un bloc with, l’exception n’est pas capturée, elle continue donc son chemin en dehors du bloc. Souvent, c’est ce qu’on attend du programme : en cas d’imprévu, on préfère sortir au plus tôt, pour limiter les dégâts. D’autre part, dans un programme où on doit « faire » puis « défaire » quelque chose, les méthodes __enter__() et __exit__() proposent une sémantique parfaite. Meilleure à mon avis qu’un bloc try...finally.

Le langage python fournit en standard des objets faisant office de context manager. C’est le cas par exemple des « objets fichiers ». Ainsi, sans connaître le détail des méthodes __enter__() et __exit__(), on peut écrire :

with open('mon_fichier.txt')as f:

   print(f.read()) # ou tout autre traitement sur f

Ici, l’avantage, c’est qu’on s’assure que le fichier sera fermé en sortie de bloc, quoi qu’il arrive.

Je ne vais pas m’étaler beaucoup plus, mais sachez qu’on peut très bien imbriquer plusieurs blocs with. L’idée, c’est qu’on va empiler des traitements via les fonctions __enter__() et les dépiler via les fonctions __exit__(). Et le dépilage interviendra donc dans tous les cas (exécution normale ou exception).

Vous comprenez donc pourquoi j’aimerais pouvoir appliquer le même principe à mon script shell debootstick. Donc essayons !

2. Bloc « with » en bash (??)

2.1 Implémentation naïve

On va commencer simplement et faire notre test à partir du code bash suivant, proche de la version Python :

#!/usr/bin/env bash

source with_bloc.sh


clouer() {

objet="$1"

if [ "${objet:0:4}" != "clou" ]

then

echo"Je ne peux pas clouer '$objet'!"

return 1 # non-nul = echec

else

echo"$objet cloué."

fi

}


prendre_marteau() {

   echo'marteau en main'

}


defaire_prendre_marteau() {

   echo'marteau rangé'

}


problemes="$@"

with prendre_marteau

   for probleme in$problemes

   do

      clouer"$probleme"

   done

end_with

echo'Au revoir!'

À la fin du code, on voit ce qui ressemble à un bloc with, entre deux balises with et end_with. Afin qu’elles soit réutilisables, je les ai définies dans un fichier with_bloc.sh séparé, importé en deuxième ligne.

La deuxième chose à remarquer dans ce code, c’est la convention que j’ai adoptée : pour pouvoir utiliser une fonction <f>() dans un bloc with, il faudra au préalable définir la fonction defaire_<f>(). Comme vous vous en doutez, cette fonction sera appelée automatiquement au moment du end_with (avec les mêmes arguments).

Avant de vous dévoiler ce que j’ai mis dans with_bloc.sh, voyons déjà si ça marche :

$ ./test.sh clou1 clou2

marteau en main

clou1 cloué.

clou2 cloué.

marteau rangé

Au revoir!

$

Apparemment, oui, en tout cas dans ce cas simple.

Voici donc le contenu de with_bloc.sh :

1  EOL="

2  "

3  a_depiler=""

4

5  with() {

6     cmd="$*"

7     $cmd

8     a_depiler+="${EOL}defaire_$cmd"

9  }

10

11 end_with() {

12    cmd_defaire="$(echo "$a_depiler" | tail -n 1)"

13    $cmd_defaire

14    a_depiler="$(echo "$a_depiler" | head -n -1)"

15 }

Comme vous voyez, il n’y a pas des masses de code ; with et end_with sont en fait de simples fonctions. L’idée générale, c’est qu’à chaque appel with(), on exécute bien sûr la commande donnée en paramètre, mais on doit aussi stocker quelque part la commande defaire_<qqchose> <args> qui sera appelée au moment du end_with. Et pour que ça fonctionne quand on a plusieurs with imbriqués, on doit stocker ces commandes sur une pile : on empile à chaque appel with(), on dépile à chaque end_with().

Pour implémenter la pile [2], j’ai choisi de simplement chaîner des éléments dans une chaîne de caractères (variable a_depiler). Chaque élément est de la forme \n<cmd>. J’ai défini le séparateur \n via la variable EOL (lignes 1 et 2), parce qu’en bash si on écrit \n on écrit en réalité les 2 caractères backslash et n. En revanche, et c’est la raison pour laquelle j’ai choisi ce caractère, en matière de traitement ligne par ligne on est plutôt bien outillé, en shell. Ce codage permet ainsi, de manière assez évidente :

- d’obtenir le dernier élément (= le haut de la pile) avec un simple tail -n 1 (ligne 12) ;

- de dépiler ce dernier élément via head -n -1 (ligne 14).

Pour ce qui est d’empiler, dans la fonction with() donc, on a juste à utiliser la concaténation de chaîne (via l’opérateur +=, ligne 8). Vous noterez que sur cette ligne on ajoute le préfixe 'defaire_' à la commande originale, dans l’esprit de la convention décrite plus haut.

En dehors de cette gestion de pile, la ligne 7 permet d’exécuter la commande donnée en paramètre du with, et la ligne 13 permet d’exécuter la commande préfixée par 'defaire_'.

2.2 Gestion des exceptions

Bon. Tout ça c’est bien beau, mais qu’est-ce qui se passe si on tente de clouer des vis ?

$ ./test.sh clou1 vis clou2

marteau en main

clou1 cloué.

Je ne peux pas clouer 'vis'!

clou2 cloué.

marteau rangé

Au revoir!

$

Mouais, c’est pas idéal... On est censé sortir du bloc with dès qu’une erreur survient, et là au contraire le programme a continué comme si de rien n’était. Il faudrait générer une exception quand on arrive sur la vis. Le problème c’est que… euh... en fait il n’y a pas de notion d’exception, en bash ! [3]

Bon. Mais à bien y réfléchir, on a quelque chose qui s’en rapproche. Un truc qu’on trouve dans tous les articles sur les « bonnes pratiques » en bash. C’est l’instruction set -e. Rajoutons-la en haut du script :

#!/usr/bin/env bash
set -e

source with_bloc.sh

[...]

Relançons le même test :

$ ./test.sh clou1 vis clou2

marteau en main

clou1 cloué.

Je ne peux pas clouer 'vis'!

$

Avouez qu’on a l’impression de sortir sur une exception ! En fait, quand on lance la commande set -e, on indique à l’interpréteur du script qu’il doit vérifier le code de retour de chaque commande. Et si ce code de retour est non nul (ce qui est un signe d’erreur), l’exécution est interrompue. Dans notre exemple, la commande return 1 de la fonction clouer() a donc suffi pour interrompre l’exécution.

Le souci dans ce cas, c’est que le marteau n’a pas été rangé. Cela implique que le code du end_with n’a pas été exécuté, contrairement à ce qui se passe avec un vrai bloc with, comme en Python.

Pour forcer l’appel du end_with en cas de sortie prématurée, il faudrait déclencher un traitement juste avant de sortir du script. Pour ce genre de choses, on utilise une fonctionnalité annexe de la commande trap. L’utilité principale de cette commande est d’associer un traitement à la réception d’un signal. Par exemple :

...

on_sigint() {

   # ... traitement SIGINT

}

trap on_sigint SIGINT

...

Mais en réalité, en deuxième paramètre, on n’est pas restreint aux seuls identifiants de signaux : la commande help trap (voir encadré) nous indique quelques possibilités supplémentaires. En particulier, on peut spécifier EXIT pour détecter la sortie du script, et donc exécuter un code adéquat juste avant de sortir.

Pourquoi « help trap » et pas « man trap » ?

Certaines commandes, comme trap, sont internes au shell. La preuve :

$ which trap

$

Il n’y a pas d’exécutable nommé trap sur le système. C’est juste bash qui l’interprète. Par conséquent, la documentation de la commande trap s’obtient par... man bash ! Le souci, c’est que la page de manuel de bash est un peu longuette. C’est pour ça que je vous proposais d’utiliser l’aide en ligne de bash, en tapant help <cmd>.

Pour corser un peu le tout, sachez qu’il y a aussi des commandes qui sont à la fois disponibles sur le système ET implémentées en interne par bash. C’est le cas de la commande echo par exemple :

$ which echo

/bin/echo

$ help echo

echo: echo [-neE] [arg ...]

   Write arguments to the standard output.

[...]

Quel intérêt me direz-vous ? La plupart du temps, c’est une question de performance. Si le shell utilise la commande système, alors il lui faut créer un processus fils à chaque fois pour la lancer, ce qui est quand même très coûteux pour une commande aussi basique. Le problème, c’est qu’il peut y avoir quelques différences subtiles entre les deux implémentations. Et au final, si vous tapez man echo, vous ne regardez sûrement pas la bonne documentation ! Il y a d’ailleurs une indication dans cette page de manuel qui devrait vous alerter à ce sujet.

Rajoutons donc le code suivant à la fin de with_bloc.sh :

on_exit() {

   while [ "$a_depiler" != "" ]

   do

      end_with

   done

}


trap on_exit EXIT

Voilà, je crois que ce code est assez évident : en sortie du script, on referme les éventuels blocs with restés ouverts. Testons :

$ ./test.sh clou1 vis clou2

marteau en main

clou1 cloué.

Je ne peux pas clouer 'vis'!

marteau rangé

$

Cette fois-ci, ça fonctionne !

2.3 Le test de trop

Il ne reste plus qu’une chose à tester : les bloc with imbriqués. Mais normalement, avec notre gestion basée sur une pile, ça devrait rouler.

Bon voilà, j’ai réécrit la fin de test.sh. Voilà les dernières lignes :

[...]

with ouvrir_tiroir

   with prendre_marteau

      for probleme in$problemes

      do

         clouer"$probleme"

      done


with prendre_telephone

   echo"allo, chef, c'est fait!"


echo'** fin du script **'

Je vous laisse imaginer le code de ouvrir_tiroir(), defaire_ouvrir_tiroir(), prendre_telephone() et defaire_prendre_telephone(). Voyons ce que ça donne à l’exécution, juste avec 2 clous pour commencer :

$ ./test.sh clou1 clou2

tiroir ouvert

marteau en main

clou1 cloué.

clou2 cloué.

telephone en main

allo, chef, c'est fait!

** fin du script **

telephone sur son socle

marteau rangé

tiroir fermé

$

?? Qu’est-ce que c’est que ce bazar ? Y aurait-il un souci dans ma gestion de pile ? (L’avis de mon relecteur est plutôt que le script a développé une intelligence propre et déduit qu’avec deux mains un humain peut très bien prendre le téléphone sans lâcher le marteau...)

Un bon quart d’heure plus tard…

Suis-je idiot ! J’ai juste oublié d’écrire les end_with !! Je pouvais toujours chercher un bug dans ma gestion de pile !

Trois balises end_with rajoutées plus tard…

C’est reparti pour le test :

$ ./test.sh clou1 clou2

tiroir ouvert

marteau en main

clou1 cloué.

clou2 cloué.

marteau rangé

tiroir fermé

telephone en main

allo, chef, c'est fait!

telephone sur son socle

** fin du script **

$

C’est mieux, beaucoup mieux ! Et avec la vis :

$ ./test.sh clou1 vis clou2

tiroir ouvert

marteau en main

clou1 cloué.

Je ne peux pas clouer 'vis'!

marteau rangé

tiroir fermé

$

Impeccable, on a juste refermé les with ouverts, avant de quitter à cause de l’exception.

3. Une intégration plus poussée dans le langage

Cette petite mésaventure d’oubli des end_with m’a fait prendre conscience d’une limite importante de mon implémentation. En Python, la fermeture du bloc with est implicite, elle s’effectue automatiquement quand on quitte le bloc indenté. De ce fait, il n’y a pas de mot-clé comme end_with à indiquer (ou à oublier, en l’occurrence). En bash, l’indentation n’influe pas sur la syntaxe, mais on a quand même des vérifications de syntaxe bien utiles sur certaines constructions. Par exemple, dans une boucle while, si jamais on oublie le done final, on aura une erreur de syntaxe, ce qui a l’avantage d’aiguiller directement le programmeur sur le bon diagnostic.

Avec mon implémentation actuelle, il paraît compliqué de déclencher une telle erreur de syntaxe. Mais si on la fait évoluer un peu, c’est peut-être possible. Je crois d’ailleurs que j’ai une petite idée qui se dessine, un genre de détournement de la boucle while...

3.1 Une boucle while contrôlée

Commençons par un petit test avec le script suivant, boucle_controlee.sh :

#!/bin/bash


boucle_controlee() {

   if [ $entree_boucle = 1 ]

   then

      echo entree du bloc $*

      entree_boucle=0

      return 0 # condition ok, re-boucler

   else

      echo sortie du bloc $*

      return 1 # condition pas ok, sortir

   fi

}


entree_boucle=1

while boucle_controlee

do

   echo dans le bloc

done

Si on identifie les itérations du while (variable entree_boucle), on peut la « contrôler » : on décide de passer le premier test, donc d’exécuter le contenu du bloc, mais d’arrêter au deuxième test. Voilà ce que ça donne à l’exécution :

$ ./boucle_controlee.sh

entree du bloc

dans le bloc

sortie du bloc

$

3.2 Un alias pour cacher ce que vous n’êtes pas censés voir

Si vous ne voyez pas où je veux en venir, modifions légèrement la chose en ajoutant la définition d’un alias :

#!/bin/bash


boucle_controlee() {

[... voir plus haut ...]

}


shopt -s expand_aliases

alias with='entree_boucle=1; while boucle_controlee'


with ici_une_commande

do

   echo dans le bloc

done

Là c’est clair, non ? Il est pas beau ce bloc with...do...done ? Et là, si on oublie de fermer le bloc avec le done, on aura effectivement une erreur de syntaxe !

On peut vérifier, à l’exécution ça marche toujours :

$ ./boucle_controlee.sh

entree du bloc ici_une_commande

dans le bloc

sortie du bloc ici_une_commande

$

Il reste deux points à éclaircir. Le premier, c’est l’emploi bizarre de la variable entree_boucle, initialisée avant la boucle while (dans l’alias) et ensuite modifiée dans la fonction boucle_controlee(). En fait, on est tenté d’implémenter ce contrôle de la boucle dans la fonction uniquement, en alternant entre deux états, pour gérer respectivement l’entrée et la sortie du bloc. En réalité, ce serait une erreur, car l’entrée et la sortie correspondent à deux appels distincts de cette fonction. Or, en cas d’imbrication de blocs with, on casse cette alternance entre entrée et sortie de bloc. Au final, je me suis donc contenté de simplement repérer l’entrée dans la boucle.

Le deuxième point concerne l’activation de l’option expand_aliases. Il faut savoir que, par défaut, bash n’interprète les alias que lors d’une session interactive. Ici, s’agissant de l’exécution d’un script, ce n’est pas le cas. Il faut donc activer cette option pour que l’alias soit pris en compte.

3.3 Implémentation finale

Notre implémentation finale de with_bloc.sh va reprendre les éléments de notre première implémentation, ainsi que l’idée développée ci-dessus.

EOL="

"

a_depiler=""


start_with() {

   cmd="$*"

   $cmd

   a_depiler+="${EOL}defaire_$cmd"

}


end_with() {

   cmd_defaire="$(echo "$a_depiler" | tail -n 1)"

   $cmd_defaire

   a_depiler="$(echo "$a_depiler" | head -n -1)"

}


on_exit() {

   while [ "$a_depiler" != "" ]

   do

      end_with

   done

}


trap on_exit EXIT


boucle_controlee() {

   if [ $entree_boucle = 1 ]

   then

      start_with $*

      entree_boucle=0

      return 0 # condition ok, re-boucler

   else

      end_with

      return 1 # condition pas ok, sortir

   fi

}


shopt-s expand_aliases

aliaswith='entree_boucle=1; while boucle_controlee'

La fonction with() de notre première implémentation a été renommée en start_with(). Excepté ce renommage, elle est inchangée. La fonction end_with() est également inchangée, tout comme le code qui ferme les blocs restés ouverts en cas d’exception (on_exit() et commande trap). La fonction boucle_controlee() a été très légèrement modifiée pour appeler les fonctions start_with() et end_with(). Enfin, la définition de l’alias reprend ce qu’on a écrit ci-dessus.

Pour tester cette version, on pourra adapter notre script de test :

#!/usr/bin/env bash

set-e

source with_bloc.sh


[... mêmes fonctions que précédemment ...]


problemes="$@"

with ouvrir_tiroir; do

   with prendre_marteau; do

      for probleme in $problemes; do

         clouer"$probleme"

      done

   done

done


with prendre_telephone; do

   echo"allo, chef, c'est fait!"

done


echo'** fin du script **'

En testant, avec ou sans la vis, on retrouve des résultats identiques à notre première implémentation. Par contre, si on oublie de fermer un des blocs avec done, l’interpréteur détecte le souci de syntaxe :

$ ./test.sh clou1 vis clou2

./test.sh: ligne 56: erreur de syntaxe : fin de fichier prématurée

$

Pour tout vous dire, c’est bien la première fois que je suis content de voir s’afficher une erreur de syntaxe. :)

Épilogue

Deux ans plus tard…

Après avoir implémenté cette gestion dans debootstick[6], j’ai pu débuguer plus rapidement. Parce qu’un outil qui met le bazar sur votre machine à chaque plantage, c’est plutôt pénible à débuguer ! D’ailleurs, phase de débugage ou pas, cela reste un outil qui enchaîne les opérations bas niveau sur votre système. Ce « filet de sécurité » est donc plutôt bienvenu.

Peu de temps après, j’ai donc pu fournir une première version, capable de prendre en charge les scénarios décrits au début de l’article. Et quelques autres joyeusetés. L’outil est maintenant disponible dans l’OS Debian (testing), depuis l’été 2015. Vous pourrez donc le trouver dans la prochaine version stable de Debian [7], « stretch », qui sera peut-être sortie quand vous lirez ces lignes.

Pour la petite histoire, quand on construit un paquet pour Debian on doit faire tourner lintian, un outil qui vérifie les sources. Comme je l’ai laissé entendre au tout début de l’article, cette construction bizarre n’est alors pas passée inaperçue ! J’ai dû ajouter ce qu’on appelle un « lintian override », une annotation indiquant à l’outil que ce qu’il a détecté n’est pas un souci.

Merci à Henry-Joseph pour la relecture !

Notes et références

[1] Vous avez pu entrevoir dans un article récent [5] cette suite d’opérations à effectuer. Notons toutefois que debootstick vise à créer des systèmes live pérennes sur le long terme, la structure de l’OS est donc plus simple (pas de squashfs), ce qui permet des mises à jour complètes (bootloader & noyau compris). D’autre part, debootstick ne travaille pas directement sur votre clé USB, il construit une image ; vous pourrez donc la tester au préalable avec kvm. En effet, flasher un OS sur une clé USB n’est pas un acte anodin vis-à-vis de la durée de vie de celle-ci.

[2] Dans la plupart des langages, pour implémenter une pile, on se base sur un tableau. Mais à mon avis, en bash, l’usage des tableaux est plutôt cryptique, surtout pour des opérations comme « supprimer le dernier élément du tableau ». Alors je ne vais pas souiller votre magazine préféré avec ce genre d’incantations.

[3] Si c’était un « article dont vous êtes le héros », ici il y aurait une première fin possible ;-)

[4] L’avis de mon relecteur est plutôt que le script a développé une intelligence propre et déduit qu’avec deux mains un humain peut très bien prendre le téléphone sans lâcher le marteau...

[5] ENDRES F., « Live-System from Scratch », GNU/Linux Magazine n°202, mars 2017, p. 54 à 61.

[6] Page GitHub de debootstick : https://github.com/drakkar-lig/debootstick

[7] Package debootstick dans Debian Stretch : https://packages.debian.org/stretch/debootstick




Articles qui pourraient vous intéresser...

Conservez l’historique de vos commandes pour chaque projet, le retour

Magazine
Marque
GNU/Linux Magazine
Numéro
241
Mois de parution
octobre 2020
Domaines
Résumé

Pouvoir conserver un historique dédié pour chaque projet, voici l’idée géniale énoncée par Tristan Colombo dans un précédent article de GLMF [1]. Cet article reprend ce concept génial (je l’ai déjà dit?) et l’étoffe en simplifiant son installation et en ajoutant quelques fonctionnalités (comme l’autodétection de projets versionnés pour proposer à l’utilisateur d’activer un historique dédié, si ce n’est pas le cas).

Système extensible et hautement disponible avec Erlang/OTP

Magazine
Marque
GNU/Linux Magazine
Numéro
241
Mois de parution
octobre 2020
Domaines
Résumé

Erlang est un langage de programmation fonctionnel et distribué, créé à la fin des années 80 pour régler de nombreux problèmes issus du monde des télécoms, et plus généralement de l’industrie. Outre le fait qu’il soit l’une des seules implémentations réussies du modèle acteur disponible sur le marché, son autre grande particularité est d’être livré avec une suite d’outils, de modèles et de principes conçus pour offrir un environnement cohérent. Ce framework, nommé OTP, fait partie intégrante de la vie des développeurs utilisant Erlang au jour le jour...

Simulation d’un ordinateur mécanique en scriptant sous FreeCAD

Magazine
Marque
Hackable
Numéro
35
Mois de parution
octobre 2020
Domaines
Résumé

L’évolution du traitement du signal est une histoire fascinante largement déroulée par David Mindell dans ses divers ouvrages [1] et citations [2]. Partant de l’ordinateur mécanique avec ses rouages, poulies, bielles et crémaillères, le passage à l’électrique au début du 20ème siècle, puis à l’électronique intégrée avec l’avènement du transistor et des circuits intégrés (VLSI) nous ont fait oublier les stades initiaux qui ont amené à notre statut actuel d’ordinateurs infiniment puissants, précis et compacts. Alors que cette histoire semble s’accompagner du passage de l’analogique au numérique – de la manipulation de grandeurs continues en grandeurs discrètes avec son gain en stabilité et reproductibilité – il n’en est en fait rien : un boulier fournit déjà les bases du calcul discrétisé mécanique, tandis que [3] introduit les concepts du calcul mécanique avec les traitements numériques avant de passer aux traitements analogiques.