Comment ranger sa chambre avec UNIX ?

C’est en faisant joyeusement la vaisselle hier que j’eus le malheur de me poser la question suivante : mes méthodes d’organisation sont pitoyables et j’aurais besoin d’un outil facile à utiliser pour pouvoir classer mes fichiers selon la date où je les ai créés. Sans vouloir utiliser quelque chose de lourd ou une API surpuissante, je me suis donc mis en tête de faire un petit outil pour classer automatiquement mes fichiers en utilisant les ressources à disposition de la plupart d’entre nous : la ligne de commande. Bien sûr l’outil n’oblige pas à utiliser une méthode particulière pour naviguer dans ses fichiers : les adeptes de la ligne de commande pourraient autant l’utiliser que les fans de gestionnaires de fichiers plus ou moins graphiques (Nautilus, PCManFM, Thunar, Konqueror, Dolphin, Krusader, mc, Finder…).

Cet article vous propose donc un voyage au pays du rangement automatique à travers une petite recherche pour développer un outil suffisamment flexible pour une utilisation quotidienne. Il peut servir autant aux amateurs de rangement qu’à des gens qui ont envie d’apprendre quelques petites choses avec bash.

Présentation

Mon outil sans prétention est un script bash à exécuter lorsqu’on le désire (je pense au démarrage de la session X ou alors à l’outil cron qui permettrait de l’exécuter tous les jours). Il permet d’utiliser une hiérarchie de dossiers où on peut ranger ses affaires du jour (Documents, Notes, Downloads) de façon transparente (dans un dossier Current). Ce dossier Current est un lien symbolique vers un dossier plus compliqué du style Sorted/février/lundi_02. Un appel au script crée un nouveau dossier si le jour a changé et met à jour une hiérarchie globale où tous les fichiers que l’on trouve pour tous les jours sont centralisés (avec des liens), ce qui permet de retrouver la hiérarchie bordélique que j’utilise chaque jour lorsque je ne me souviens plus de quand j’ai fait tel fichier qui m’intéresse. C’est avant tout un outil pour traiter le travail quotidien et il ne garantit pas d’être utilisable pour archiver des documents. Mais je pense qu’avec quelques améliorations il pourrait être utilisable.

Utilisation

Comment utiliser ce petit script ? C’est très simple : on commence par créer un répertoire vierge et décompresser l’archive du script que l’on trouve ici.

$ mkdir Test
$ cd Test/
$ tar xvzf <chemin>/joinUpdate-pre.tar.gz

L’archive contient deux fichiers : Update qui est le script à appeler, et functions.sh qui est un script auxiliaire contenant des fonctions utiles.

On commence par créer la hiérarchie de répertoires :

$ ./Usage
Creating the Sorted directory...
Creating the Template directory...
Creating the Global directory...
Creating current date directory...
Current directory changed to current date...

Que sont ces répertoires ? On en a trois bien utiles :

  • Sorted est le répertoire qui stocke tous les fichiers. On ne dirait pas, parce qu’on ne les utilise jamais directement, sauf pour faire ce pourquoi j’ai créé tout ça : pour naviguer dans ses fichiers classés par jour. Dans le répertoire Sorted on aura donc les répertoires février et février/lundi_02.
  • Template est le répertoire que vous allez devoir remplir avec les répertoires que vous voulez centraliser : Documents, Downloads, Projet1, etc. Vous mettez vos répertoires (vides) et ils seront copiés tous les jours, vous assurant ainsi de retrouver toujours les mêmes répertoires tous les jours pour pouvoir encore plus les remplir !
    Si vous avez bien pigé, alors dans votre répertoire Sorted/février/lundi_02 vous aurez les mêmes répertoires que dans Template
  • Global est le répertoire qui rassemblera des liens vers tous les fichiers que vous avez créé dans Sorted depuis le début.
  • Current est un lien symbolique vers le répertoire du jour courant. C’est celui-là que vous utiliserez dans vos travaux journaliers pour stocker vos fichiers.

On a donc une hiérarchie toute propre, il faut remplir le répertoire de templates :

$ mkdir Templates/Documents
$ mkdir Templates/ProjetJoin

Ensuite on doit appeler le script Update afin qu’il copie ces modèles dans les répertoires Global et Current.

$ ./Update
Documents added to Global from Template.
ProjetJoin added to Global from Template.
Documents updated in Current.
ProjetJoin updated in Current.

Voilà une affaire rondement menée ! Maintenant on peut utiliser le répertoire Current avec plusieurs programmes, on navigue un peu sur Internet, on enregistre quelques pages, tout ça tout ça… Pour l’exemple on va faire un peu plus simple, avec la ligne de commande :

$ echo "Idée 1" > Current/ProjetJoin/idee
$ echo "Bonjour." > Current/Documents/hello
$ echo "12345" > "Current/Documents/Machin Bidule"

Une fois nos documents créés, on peut appeler Update pour qu’il crée les liens vers ces fichiers dans le répertoire Global (on doit le faire dans la journée, car le script ne crée les liens que vers les fichiers de la journée en cours).

$ ./Update
Checking /home/join/src/lntest/Current/Documents...
Checking file hello... Linked into Global.
Checking file Machin Bidule... Linked into Global.
Checking /home/join/src/lntest/Current/ProjetJoin...
Checking file idee... Linked into Global.

Et voilà ! Regardez ce qu’il y a dans Global :

$ find Global/
Global/
Global/Documents
Global/Documents/hello
Global/Documents/Machin Bidule
Global/ProjetJoin
Global/ProjetJoin/idee

Parfait. Si on regarde mieux, tous les fichiers sont des liens symboliques. C’est ce qui m’a amené à considérer un premier problème : dans un but d’intuitivité, il aurait fallu que lorsqu’un fichier de Global est enlevé, sa référence soit enlevée aussi. Malheureusement, pour faire ceci, je n’ai pas encore trouvé une solution sympatique, et puis on supprime toujours trop rapidement. À l’heure où la capacité des supports de stockage est très grande (plus d’un Gio, on va dire), on peut quand même se permettre de ne presque jamais supprimer nos fichiers, tant qu’ils sont rangés (et ils le seront, puisque c’est le but de cet outil). Il faut garder à l’esprit que cet outil est adapté à des petits fichiers comme des travaux que l’on fait (textes, code, graphisme, etc.).

Mises à jour et conflits de noms

Donc là tout est « synchronisé », mais il y a une question qui pose problème (encore !) : si deux fichiers ont le même nom (dans deux jours différents), que se passe-t-il dans Global ? J’ai choisi la solution de facilité (laisser le premier fichier et ne pas mettre l’autre) mais elle pourrait s’avérer avantageuse ou désavantageuse selon les cas (elle force à bien nommer ses fichiers, mais en contrepartie le fait que le dernier fichier créé écrase le reste permettrait de garder des copies de sauvegarde des fichiers et ainsi de pouvoir voir a posteriori leur progression). Faisons un essai et forçons un nouveau Update :

$ ./Update
Checking /home/join/src/lntest/Current/Documents...
Checking file hello... Skipping (exists).
Checking file Machin Bidule... Skipping (exists).
Checking /home/join/src/lntest/Current/ProjetJoin...
Checking file idee... Skipping (exists).

Il ne met aucun lien à jour. Le comportement est bien celui escompté. Afin de garantir l’intégrité de notre répertoire Global, supprimons un fichier dans Current :

$ rm "Current/Documents/Machin Bidule"

Le lien symbolique est maintenant invalide :

$ cat "Global/Documents/Machin Bidule"

cat: Global/Documents/Machin Bidule: Aucun fichier ou dossier de ce type

Un petit appel à Update corrigera tous nos problèmes.

$ ./Update
Checking /home/join/src/lntest/Current/Documents...
Checking file hello... Skipping (exists).
Checking /home/join/src/lntest/Current/ProjetJoin...
Checking file idee... Skipping (exists).
Bad link for /home/join/src/lntest/Global/Documents/Machin Bidule. Deleting.

Ok, très bien. Je vous laisse le soin d’apprécier un usage quotidien du script, vous pourrez même le bidouiller.

Des problèmes, que des problèmes !

Cet outil contre-révolutionnaire présente néanmoins quelques inconvénients :

  • Il vérifie tous les fichiers du jour à chaque exécution. Il faut donc définir un mode où il ne fait que des vérifications, et l’exécuter soit au login et à la déconnexion, soit toutes les heures (ou toutes les 5 minutes par exemple).
  • Il ne résout pas le problème des conflits lors des mises à jours. J’attends de pouvoir trouver une solution viable (et pas de renommage de fichiers, c’est déplaisant).
  • L’utilisation de deux hiérarchies distinctes, mêmes synchronisées, pose des problèmes pour les utilisateurs qui se servent de la fonction « Derniers documents ouverts », à moins que les programmes implémentant cette fonction suivent les liens symboliques et ne gardent que l’adresse de la source. Parce que vu que Current est changé tous les jours, ça pose des problèmes…
  • L’utilisation de liens symboliques, c’est bien, mais des liens en dur c’est peut-être aussi bien, voire mieux. Le problème viendra dans la réplication de hiérarchies : avec des liens symboliques, on n’a qu’à faire des liens avec tout ce qui passe, que ce soient des fichiers ou des répertoires, et ça ne pose pas de problèmes pour les répertoires. On ne peut pas faire de lien vers des répertoires en dur. Il faut soit les copier, soit en faire des liens symboliques.
  • Que dire de l’utilisation du système de fichiers ? C’est une solution simple, mais peut-être que certains systèmes de fichiers n’aiment pas l’abondance de répertoires que cela va générer.
  • Des systèmes de contrôle de version (CVS, SVN, Git…) pourraient faire ce genre de choses et plus encore, moyennant quelques lectures. C’est hélas un peu plus compliqué à appréhender, c’est pas trop fait pour ça et on perd un peu de « sens ».
  • Le système actuel est trop attaché aux chemins absolus (mais ce n’est peut-être pas si grave, finalement).
  • Au final, par rapport à un outil qui devrait scruter toutes les modifications comme un démon, on n’est pas loin. Et c’est pas très optimisé… Bref, il n’y a peut-être pas de solution qui n’impose pas d’utiliser un démon.

Vous remarquez que j’ai pris le problème dans un sens particulier : le répertoire de bordel contient les liens et le répertoire rangé contient les vrais fichiers, ce qui semblait pratique au premier abord. J’aurais pu faire ça dans l’autre sens, mais une question se pose : je range les fichiers de cette façon (par date, mais j’aurais pu trouver d’autres critères pas forcément temporels) parce que les propriétés des fichiers imposées par le système de fichiers (utilisateur, date de modification, de création) ne me conviennent pas pour trier mes fichiers (et de toute façon je ne connais pas d’outil intuitif et rapide permettant de les grouper grâce à un critère). En regardant le problème dans l’autre sens, comment reconnaître dans un bordel monstre les fichiers qu’on a touché aujourd’hui, sans pouvoir regarder les critères que je viens de citer (surtout la date de modification) ? Si on laisse tomber l’idée de devoir appeler le script à chaque ajout d’un fichier dans le répertoire qu’on veut surveiller (pour le transférer directement), on peut par contre faire de la surveillance de répertoire. Cela nécessite un fichier supplémentaire mais au moins on peut directement garder la trace de toutes les modifications : les fichiers enlevés et les fichiers rajoutés. À la mise à jour (appel du script), il suffit de comparer le dernier fichier chargé de surveiller le répertoire à la hiérarchie de fichiers enregistrés et le compte est bon ! Comment faire ceci ? Voici une solution.

Utiliser find

Nous pouvons obtenir la hiérarchie de fichiers à partir d’un répertoire grâce à la commande find. Créons une hiérarchie de test et enregistrons la hiérarchie (produite par find) dans un fichier à l’extérieur de cette hiérarchie.

$ mkdir test; cd test
$ mkdir rep1 rep2
$ touch fich1 fich2 rep1/fich1 rep1/fich2 rep2/fich1
$ find . > ../hierarchie

Maintenant, supprimons quelques fichiers, rajoutons-en d’autres et observons le résultat.

$ rm rep1/fich2 fich1
$ touch rep1/nouveau pouet
$ find . > ../hierarchie2

On compare les deux :

$ diff ../hierarchie ../hierarchie2 | grep [\<\>]
< ./rep1/fich2
< ./fich1
> ./rep1/nouveau
> ./pouet

Et voilà, on a toutes les modifications de la structure de la hiérarchie de fichiers qui ont été effectuées entre les deux find. On utilise grep pour ne garder que les lignes que l’on veut (diff produit des lignes supplémentaires qui sont des scripts ed, et qui ne nous intéressent pas). Donc si on veut récupérer la liste des fichiers qui ont été supprimés (et donc la liste des fichiers dont le lien symbolique doit être effacé dans la hiérarchie qu’on va maintenir), on fait comme ceci (remarquez ce qui change dans le grep) :

$ diff ../hierarchie ../hierarchie2 | grep [\<] | cut -c 3-
./rep1/fich2
./fich1

L’appel de cut est nécessaire pour enlever les petits signes < ou > qui polluent le nom du fichier.

On peut même faire encore plus fort et n’utiliser que des chemins absolus grâce au programme readlink. Donc au lieu de :

$ find . > ../hierarchie

On ferait :

$ find . | xargs -n1 readlink -f > ../hierarchie

Ici on utilise xargs qui transforme selon des paramètres qu’on définit l’entrée standard en arguments passés à un programme. On doit faire cela car readlink ne joue pas avec l’entrée standard.

Avec tout ça, on pourrait tenter un programme sympathique.

Un nouvel essai

Nous allons donc essayer de suivre le développement inverse : faire un programme qui, à partir d’une hiérarchie de répertoires bordéliques, les classe dans des répertoires selon des critères. Comme critère, on va prendre la date du jour, mais on peut en réalité faire ce que l’on veut. On peut même imaginer que le critère soit une activité donnée par un nom, et qu’une applet GNOME (par exemple) puisse permettre de changer d’activité, occasionnant ainsi la classification dans la nouvelle activité de tous les nouveaux fichiers de la hiérarchie de répertoires à partir de cette date.

On remarque donc que cette manière de procéder est peut-être pas mal plus flexible, puisqu’on n’aurait plus besoin de templates à tenir à jour : tout ce qui est ajouté est pris en compte, donc la structure est extensible à l’infini.

Un peu plus de classe

Après une petite session développement, j’ai fini un outil qui me semble bien. Par rapport au premier jet, il est déjà un peu différent : il suit le principe inverse ! Si je suis déçu de ne pas avoir réussi à utiliser la petite astuce du répertoire Current dont j’étais fier jusque là, je trouve cette nouvelle solution bien plus élégante. Elle utilise une hiérarchie beaucoup moins lourde, moins de répertoires, moins de liens et juste un tout petit peu plus de code.

Je donne un rapide tour d’horizon de la chose. Le programme se nomme toujours Usage sauf que voici son fonctionnement :

$ ./Update -h
Usage: Update [OPTION]
Updates a sorted reference to a file hierarchy grouping them by date or label

        -s              Delete bad symlinks in the sorted reference
        -e              Delete empty directories in the sorted reference
        -l LABEL        Set a particular label

Typical call : Update

Configuration file : ~/.update_postrc
Configuration options (example) :
        SORT_DIR=/home/me/WellStoredFiles
        GLOBAL_DIR=/home/me/ReallyMessyFolder
        dir_cpt=ALabelNameIfYouDontWantToUseDates
(bash syntax)

Haha, ça vous la coupe, non ? Bon, ok… Alors donc voilà. On appelle Update comme avant, sans arguments ni rien. On peut lui passer en paramètre une option ou deux pour lui forcer à faire des opérations de maintenance, dont vérifier la validité des liens symboliques (pas mal utile si on veut garder une hiérarchie à jour), ou alors supprimer les répertoires vides (ce n’est pas obligatoire mais ça peut servir). Une option spéciale permet d’assigner une étiquette aux modifications en cours au lieu d’une date. Il est également possible de définir un fichier de configuration dans son répertoire personnel (dans ~/.update_postrc), ce qui permet de redéfinir les répertoires par défaut de rangement (SORT_DIR) et source (GLOBAL_DIR) puisqu’ils suivent le répertoire d’exécution de Update par défaut. On peut aussi définir dans ce fichier la variable d’environnement dir_cpt qui permet de donner, si on y touche, une étiquette aux prochaines modifications. Afin de faire quelque chose avec ces étiquettes, souvenez-vous que le fichier de configuration est un fichier script bash, vous pouvez donc y importer un fichier, faire des calculs, regarder des dates, ou des tas d’autres choses pour déterminer l’étiquette en cours (ce script est exécuté lors de l’appel à Update).

Ainsi le fait d’utiliser des dates ou des étiquettes a peu d’importance, et ce qui compte c’est l’effet du script. Allons faire un petit essai.

Essai du nouveau script

Voici un petit essai peu commenté du script. D’abord, on a téléchargé l’archive contenant le script Update et on va s’apprêter à créer un dossier de test pour y enfermer ce script et des répertoires divers.

$ tar xvzf <dossier>/joinUpdate-post.tar.gz
$ ./Update
Creating the Sorted directory...
Creating the Global directory...
Creating a new snapshot...
Writing out snapshot date...
Creating a new directory in the sorted structure : 2009_février/02_lundi...

Content de moi, je regarde un peu mon répertoire courant : je trouve deux répertoires qui sont Global et Sorted. Le premier me sert donc pour stocker mes hiérarchies de répertoire et tout mon travail, alors que le deuxième permet de trier tout le bordel par des appels à Update.

On fait quelques fichiers puis on met à jour la structure :

$ mkdir Global/Documents
$ touch Global/Pouetpouet Global/NoteDirection Global/Documents/truc
$ ./Update
Adding ./Pouetpouet...
Adding ./Documents...
Adding ./Documents/truc...
Adding ./NoteDirection...

Génial ! Ça a tout ajouté dans Sorted/2009_février/02_lundi. Ah… J’ai oublié d’ajouter un fichier. Faisons-le.

$ touch Global/oubli
$ ./Update
Adding ./oubli...

Et voilà. Maintenant nous voulons faire une session de travail spécialement pour les minutes à venir. Nous allons utiliser l’option -l avec tous les prochains appels au script pour spécifier une étiquette.

$ touch Global/travailsession Global/jeusession
$ ./Update -l session
Adding ./jeusession...
Adding ./travailsession...
$ ls Sorted/session/
jeusession  travailsession

Ok, maintenant nous n’avons plus envie d’être dans cette session de travail. Faison un Update normal après quelques modifications.

$ rm Global/oubli Global/travailsession
$ touch Global/nouveau
$ ./Update
Creating a new snapshot...
Writing out snapshot date...
Removing ./travailsession...
Removing ./oubli...
Adding ./nouveau...

Voilà donc un outil qui pourrait satisfaire plusieurs utilisations.

Perspectives

Bien sûr, il manque à notre outil une licence un peu plus solide, la localisation (je l’ai fait en anglais pour pouvoir partager plus facilement le code si jamais il servait), et quelques fonctions, mais il m’a l’air bien parti pour une utilisation quotidienne et c’est ce que je vais tester de ce pas. Je ne livre pas la solution clés en main mais il est très facile d’utiliser l’outil pour faire des applets d’environnement de bureau, des démons et toutes sortes de choses.

J’attends toutes vos remarques sur cet article qui m’aura décidément pris plus de temps que prévu.

Petite modification

J’interviens pour une modification de parcours : j’ai rajouté quelques petites lignes pour pouvoir placer la hiérarchie rangée (répertoire Sorted) à l’intérieur de la hiérarchie bordélique (répertoire Global).

Téléchargement

  • Version pre (la première qui utilisait les dossiers rangés comme référence)
  • Version post (la deuxième qui utilise les dossiers bordéliques comme référence)

Ce travail est soumis à la WTFPL. Respectez-la🙂.

3 Responses to “Comment ranger sa chambre avec UNIX ?”


  1. 1 une fille février 13, 2009 à 11:19

    j’ai détesté sa (horible)

  2. 2 join février 14, 2009 à 1:02

    Ah c’est marrant, moi je déteste plutôt les commentaires peu constructifs…

  3. 3 playmobitch février 16, 2009 à 9:03

    elle a du se tromper de skyblog…


Laisser un commentaire

Entrez vos coordonnées ci-dessous ou cliquez sur une icône pour vous connecter:

Logo WordPress.com

Vous commentez à l'aide de votre compte WordPress.com. Déconnexion / Changer )

Image Twitter

Vous commentez à l'aide de votre compte Twitter. Déconnexion / Changer )

Photo Facebook

Vous commentez à l'aide de votre compte Facebook. Déconnexion / Changer )

Photo Google+

Vous commentez à l'aide de votre compte Google+. Déconnexion / Changer )

Connexion à %s





%d blogueurs aiment cette page :