La Case de l'Oncle Tom

Bash : date du plus récent fichier d’un répertoire

  • 14 septembre 2007
  • 💬 9
  • 📒 6964

Ce qu’il y a de génial avec les systèmes Unix (dont Linux fait partie), c’est la possibilité d’interagir avec le système et d’étendre ses possibilités en jouant avec les programmes à disposition. Je pense qu’il est indispensable pour un développeur Web de connaître les bases de ces systèmes non pas pour frimer mais pour se simplifier la vie.
L’Internet n’aura de cesse de progresser et les langages d’évoluer, il n’y aura probablement jamais plus simple ni plus rapide que de passer par les interactions système pour arriver à ses fins.

Ma problématique était simple : j’avais besoin de récupérer la date du plus récent fichier d’un répertoire. En une ligne une seule et sans boucle, la puissance de la ligne de commande m’a donné le résultat. Explication pas à pas car ça peut servir à tout le monde.

Les fichiers

Sans plus tarder, voici la liste des fichiers sur laquelle nous allons travailler :

$ls -go
-rw-r--r-- 1 0 1990-06-01 00:00 antiquite.ok
-rw-r--r-- 1 0 2007-01-01 00:00 assez_vieux.ok
-rw-r--r-- 1 0 2007-09-13 15:12 recent.bad
-rw-r--r-- 1 0 2006-01-01 00:00 tres_vieux.ok

Ce classement est de toute évidence par ordre alphabétique et indique la date de dernière modification de chaque fichier. Si certains s’étonnent des dates très rondes, sachez que la commande touch permet de forcer une date de création comme ceci : touch -d 1990-06-01 antiquite (date au format anglais, année-mois-jour). Un mystère de levé ;)

Les clés du succès : pipes, ls, grep, head et awk

N’ayez pas peur, nous allons creuser ensemble l’intégralité de ce titre pour justement pouvoir s’en servir à bon escient.

Les pipes (symbolisés par des |, tuyaux en anglais) permettent de transférer des sorties vers la prochaine commande située sur la droite. C’est ce qui permet de chaîner les commandes en filtrant les résultats de manière naturelle au fur et à mesure. Pour bien comprendre, lisez la suite.

Je recommande aux débutants de toujours s’armer d’une console à portée de main pour utiliser les manuels intégrés (man programme). Ces derniers définissent toutes les options possibles et agrémentent le tout d’exemples rendant leur compréhension plus aisée.
Ca m’évitera aussi d’avoir à expliquer tous les basiques pour ne m’attarder que sur la méthode. En revanche je répondrai volontiers à vos remarques et critiques en commentaire.

Première étape : lister le contenu (ls)

Pour obtenir le fichier le plus récent, l’idéal serait de pouvoir trier par ordre chronologique. Et mieux encore, le nirvana serait d’avoir le fichir le plus récent d’abord. Ca tombe bien, ls fait tout ça grâce à l’option -t. Mettons à jour le code :

$ ls -got
-rw-r--r-- 1 0 2007-09-13 15:12 recent.bad
-rw-r--r-- 1 0 2007-01-01 00:00 assez_vieux.ok
-rw-r--r-- 1 0 2006-01-01 00:00 tres_vieux.ok
-rw-r--r-- 1 0 1990-06-01 00:00 antiquite.ok

Deuxième étape : filtrer le contenu (grep)

Cette étape est optionnelle mais bien souvent le fichier le plus récent n’est pas celui qui nous intéresse. En l’occurence on voudrait le fichier le plus récent qui se termine en .ok. La commande grep permet d’appliquer des expressions régulières sur une liste pour n’en retourner que les éléments concordants.
C’est également notre premier cas d’utilisation des pipes et vous allez vite comprendre son fonctionnement :

$ ls -got | grep \.ok$
-rw-r--r-- 1 0 2007-01-01 00:00 assez_vieux.ok
-rw-r--r-- 1 0 2006-01-01 00:00 tres_vieux.ok
-rw-r--r-- 1 0 1990-06-01 00:00 antiquite.ok

Nombre de programmes prennent des fichiers comme dernier argument, pour savoir sur quoi travailler. Quand celui-ci n’est pas spécifié, ils considèrent alors qu’ils travaillent sur un flux de données en entrée. C’est ce que fait le pipe en redirigeant les données en entrée de la prochaine commande.

En tous cas on est arrivé là où on le souhaitait : nous avons remonté le fichier le plus récent en premier.

Troisième étape : éliminer les résultats (head)

head est un outil très sympathique dont l’utilité se suggère rien qu’à son nom : il retourne autant de lignes qu’on lui spécifie. Vous l’aurez deviné, on veut qu’il ne nous en retourne qu’une seule.

$ ls -got | grep \.ok$ | head -1
-rw-r--r-- 1 0 2007-01-01 00:00 assez_vieux.ok

On touche presque au but, il ne nous manque plus qu’à ne remonter que la date … le reste on s’en fiche.

Quatrième étape : choisir la donnée (awk)

awk est surement un des programmes les plus puissants dans la manipulation des flux de données. Il permet de découper des chaînes en segments individuels et tous réutilisables. Par défaut, il considère que le séparateur est le caractère espace. En jetant un oeil au précédent résultat, on remarque que la date se situe à la quatrième colonne.
La syntaxe quelque peu particulière d’awk fonctionne par paire de valeurs situées entre accolades du genre {action valeur}. Donc c’est parti, on affiche la quatrième colonne :

$ ls -got | grep \.ok$ | head -1 | awk '{print $4}'
2007-01-01

Conclusion

Le résultat peut paraître complexe au premier abord mais avec un peu de gymnastique et d’habitude, c’est un gain de temps assuré et qui pourra vous servir dans bien des cas : synchroniser votre application principal avec plusieurs machines distantes, supprimer des fichiers plus anciens que -n jours etc.
Une ligne contre un programme, le choix est vite fait d’autant plus que c’est rapide, peu gourmand et déclenchable depuis un langage de programmation pour peu que son paramétrage autorise l’appel de commandes système.

En cadeau bonus, voici un moyen de récupérer le résultat ci-dessus dans une variable :

date_recente=` ls -got | grep \.ok$ | head -1 | awk '{print $4}'`

Combiné au programme find et à touch (évoqué plus haut), on peut facilement récupérer un listing de fichiers plus récents ou plus anciens qu’une date en question :

date_recente=` ls -got | grep \.ok$ | head -1 | awk '{print $4}'`
touch -d ${date_recente} fichier_temoin
find /var/log -newer fichier_temoin

Commentaires

  1. LHB dit :

    Salut,
    sympa de proposer des ptites fonctions ligne de commande
    Tu devrais aussi proposer de mettre en alias, d’en faire une fonction avec comme variable l’extension et pour ceux qui n’ont pas awk un petit | cut -d’ ‘ -f8 (avec le 8 qui peut changer suivant le ls -xxxx) =)

  2. Oncle Tom dit :

    Et bien écoute je partage avec plaisir d’autant plus que ça me sert d’aide-mémoire.
    Tes suggestions sont en tous cas pertinentes. Resterait plus qu’à en faire un script Nautilus par la même occasion ;)
    Ceci dit c’était davantage pour expliquer le fonctionnement et que ce n’est pas si sorcier que ça.

  3. error3 dit :

    arf cool que tu te mettes aux commandes :-) c’est vrai que c’est vraiment génial.
    Voici comment j’aurais fait ce que tu as fait :
    ls -got|tr -s ‘ ‘| cut -d ‘ ‘ -f4,5|head -2|tail -1|sed ‘s/^\(.*\)-\(.*\)-\(.*\) \(.*\)$/\4 \3-\2-\1/’

    J’ai une grosse préférence pour sed par rapport à awk (c’est plus une question d’habitude qu’autre chose). Dans la commande ci-dessus, j’affiche l’heure avant la date, et la date au format français… Le truc purement inutile et qui peut-être fait autrement

    Allez pour ce faire plaisir, la commande qui tue (faut avoir bash et zenity):
    file=’Get Ze Date’;(echo -ne ‘#!/bin/bash\nif [ -z "$*" ]; then\n’; echo « zenity –info –text \ »\$(ls -got|tr -s ‘ ‘| cut -d ‘ ‘ -f4,5|head -2|tail -1|sed ‘s/^\(.*\)-\(.*\)-\(.*\) \(.*\)$/\4 le \3-\2-\1/’)\ »"; echo fi) > « $file »; chmod 755 « $file »; mv « $file » « $HOME/.gnome2/nautilus-scripts/ »;unset file

    Voilà normalement ça passe, mais je pense pas que le script est minimal, on doit pouvoir faire beaucoup mieux :-)
    Sinon, plutôt que nautilus-scripts, je conseille plutôt nautilus action, c’est plus puissant quand même :-)

  4. error3 dit :

    zût j’ai oublié le tr ‘:’ ‘h’ pour changer le ‘:’ en ‘h’ pour l’heure :-)

  5. Oncle Tom dit :

    J’aurais eu besoin de retravailler la date effectivement sed aurait été mieux. Mais là en l’occurence c’est l’idéal car je peux réutiliser la date dans une autre commande.

    D’ailleurs toute à l’heure je cherchais à remplacer le caractère CR (\r) d’un fichier avec sed mais je n’ai pas réussi. Pourtant j’ai tenté la recherche \ ou \x0D (ou \xOA je ne sais plus) mais rien à faire :/

  6. error3 dit :

    si c’est juste pour le remplacer par un autre, il vaut mieux utiliser tr de toute façon.
    ex: echo -e ‘hello\r\nworld’| tr ‘\r’ ‘\n’
    (pour remplacer le \r par un \n) ou si tu veux juste le virer : tr -d ‘\r’

    sinon pour sed ça donnerait ça tout simplement
    echo -e ‘hello\r\nworld’ | sed ‘s/\r/\n/g’
    ou encore sed ‘s/\r//g’ si tu veux l’enlever.
    C’est un peu plus embêtant si tu cherches à virer le \n car sed fonctionne par ligne. Cependant c’est tout de même possible de virer les \n :-) , il doivent en parler dans le man

  7. error3 dit :

    echo -e ‘hello\r\nworld’ | sed ‘s/\x0D/a/’
    sinon ça à l’air de fonctionner pourtant avec \x0D

  8. Oncle Tom dit :

    Ben écoute c’est ce que je faisais hier mais il ne voulait rien savoir :/ le \n je l’ai dégagé en faisant sed -e ‘s/$//g’

  9. error3 dit :

    ah curieux, normalement sed ‘s/$//g’ ne donne pas le résultat que tu veux car sed fonctionne ligne par ligne…
    Pour virer le \n normalement il faut faire comme ça :
    echo -e ‘toto\ntutu\ntralalala\n\ntutu’|sed ‘:a;N;$!ba;s/\n//g;P;D’

Laisser un commentaire

Votre adresse de messagerie ne sera pas publiée.

Vous pouvez utiliser ces balises et attributs HTML : <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <strike> <strong>

Notify me of followup comments via e-mail. You can also subscribe without commenting.


Propulsé par WordPress, Blueprint et WP-LESS.