Licence CC BY-NC-ND Thierry Parmentelat

parcours de dossier#

en 2024 tous les calculs/parcours sur le contenu du disque, dossiers, fichiers, et métadonnées telles que tailles, dates, etc… se font avec le couteau suisse pathlib

lisez-bien tout le notebook, et surtout les indices, avant de commencer

from pathlib import Path

(et non plus avec os.path et autres glob comme on aurait pu le faire dans le passé)

initialisation#

on va partir d’un dossier avec un peu de contenu; c’est pour simuler par exemple un dossier avec des logs

# on nettoie bien tout pour être sûr
!rm -rf pathlib-foo
from files_sortby import init
init()
Wrote file pathlib-foo/logs/file1 - size 11
Wrote file pathlib-foo/logs/dir1/filebxxxxxx - size 12
Wrote file pathlib-foo/logs/dir1/filebbxxxxxx - size 13
Wrote file pathlib-foo/logs/dir1/filebbbxxxxxx - size 14
Wrote file pathlib-foo/logs/file10 - size 10
Wrote file pathlib-foo/logs/dir10/fileaxxxx - size 102
Wrote file pathlib-foo/logs/dir10/fileaaxxxx - size 103
Wrote file pathlib-foo/logs/dir10/fileaaaxxxx - size 104
Wrote file pathlib-foo/logs/file100 - size 9
Wrote file pathlib-foo/logs/dir100/filecxx - size 1002
Wrote file pathlib-foo/logs/dir100/fileccxx - size 1003
Wrote file pathlib-foo/logs/dir100/filecccxx - size 1004

pb1: parcours de dossier#

on veut parcourir tout un dossier, c’est-à-dire calculer la liste des fichiers qui se trouvent dans un dossier; et cela récursivement ou pas (en parcourant ou non les sous-dossiers)

on vous demande d’écrire une fonction scan_dir qui prend en paramètres:

  • root (le nom d’)un dossier racine

  • relative: un chemin relatif (en dessous de cette racine; peut être vide ou ‘.’)

  • un booléen recursive

et qui renvoie une liste d’objets de type Path, qui correspondent aux fichiers (pas les dossiers) qui se situent en dessous de root/relative

paramètres keyword-only

dans la correction, nous allons utiliser un trait qui s’appelle les paramètres keyword-only
car autant le premier paramètre a un rôle parfaitement clair, autant les deux autres sont relativement accessoires;
aussi pour être sûr de ne pas se tromper, on va imposer à l’appelant de les nommer
cela signifie qu’on ne pourra pas appeler

# ceci ne sera pas légal
scan_dir("/Users/Jean Mineur", "git", True)

# il faudra toujours nommer comme ceci
scan_dir("/Users/Jean Mineur", relative="git", recursive=True)

générateur ?

pour les avancés, plutôt que de renvoyer une liste vous pouvez sans souci écrire un générateur

exemples#

from files_sortby import scan_dir

for p in scan_dir("pathlib-foo/", relative="logs/dir1", recursive=False):
    print(p)
pathlib-foo/logs/dir1/filebbbxxxxxx
pathlib-foo/logs/dir1/filebxxxxxx
pathlib-foo/logs/dir1/filebbxxxxxx
for p in scan_dir("pathlib-foo/", relative="logs", recursive=True):
    print(p)
pathlib-foo/logs/file100
pathlib-foo/logs/file1
pathlib-foo/logs/file10
pathlib-foo/logs/dir10/fileaaaxxxx
pathlib-foo/logs/dir10/fileaxxxx
pathlib-foo/logs/dir10/fileaaxxxx
pathlib-foo/logs/dir1/filebbbxxxxxx
pathlib-foo/logs/dir1/filebxxxxxx
pathlib-foo/logs/dir1/filebbxxxxxx
pathlib-foo/logs/dir100/filecxx
pathlib-foo/logs/dir100/filecccxx
pathlib-foo/logs/dir100/fileccxx

pb2: idem mais en triant#

on veut maintenant pouvoir trier cette information

on veut écrire une fonction sort_dir qui prend les mêmes paramètres, et en plus

  • un paramètre by (une chaine) qui vaut

    1. name pour trier selon le nom du fichier

    2. namelen pour trier par la longueur du nom du fichier

    3. size pour trier selon la taille du fichier

    4. mtime pour trier selon la date de modification du fichier

exemples#

from files_sortby import sort_dir

sort_dir("pathlib-foo", relative="logs", recursive=True, by='size')
[PosixPath('pathlib-foo/logs/file100'),
 PosixPath('pathlib-foo/logs/file10'),
 PosixPath('pathlib-foo/logs/file1'),
 PosixPath('pathlib-foo/logs/dir1/filebxxxxxx'),
 PosixPath('pathlib-foo/logs/dir1/filebbxxxxxx'),
 PosixPath('pathlib-foo/logs/dir1/filebbbxxxxxx'),
 PosixPath('pathlib-foo/logs/dir10/fileaxxxx'),
 PosixPath('pathlib-foo/logs/dir10/fileaaxxxx'),
 PosixPath('pathlib-foo/logs/dir10/fileaaaxxxx'),
 PosixPath('pathlib-foo/logs/dir100/filecxx'),
 PosixPath('pathlib-foo/logs/dir100/fileccxx'),
 PosixPath('pathlib-foo/logs/dir100/filecccxx')]
from files_sortby import sort_dir

sort_dir("pathlib-foo", relative="logs", recursive=True, by='namelen')
[PosixPath('pathlib-foo/logs/file1'),
 PosixPath('pathlib-foo/logs/file10'),
 PosixPath('pathlib-foo/logs/file100'),
 PosixPath('pathlib-foo/logs/dir100/filecxx'),
 PosixPath('pathlib-foo/logs/dir100/fileccxx'),
 PosixPath('pathlib-foo/logs/dir10/fileaxxxx'),
 PosixPath('pathlib-foo/logs/dir100/filecccxx'),
 PosixPath('pathlib-foo/logs/dir10/fileaaxxxx'),
 PosixPath('pathlib-foo/logs/dir10/fileaaaxxxx'),
 PosixPath('pathlib-foo/logs/dir1/filebxxxxxx'),
 PosixPath('pathlib-foo/logs/dir1/filebbxxxxxx'),
 PosixPath('pathlib-foo/logs/dir1/filebbbxxxxxx')]

Indices#

# on utilise ici quelques traits de `pathlib.Path`

from pathlib import Path

# pour construire un Path
root = Path("pathlib-foo")
root
PosixPath('pathlib-foo')
# on peut utiliser l'opérateur `/` pour construire des chemins
# mais ça ne marche pas (évidemment) entre deux chaines
# par contre dès qu'un des deux opérandes est un Path:

path = root / "logs/dir100/filecxx"
path
PosixPath('pathlib-foo/logs/dir100/filecxx')
# ou encore, donne le même résultat

path = root / "logs" / "dir100" / "filecxx"
# pour avoir la taille
path.stat().st_size
1002
# pour chercher les fichiers on peut utiliser la méthode glob
# 2 remarques:
# - ici j'appelle list(), c'est juste pour avoir un affichage correct (essayez de l'enlever...)

logs = root / "logs"

list(logs.glob("*"))
[PosixPath('pathlib-foo/logs/file100'),
 PosixPath('pathlib-foo/logs/dir10'),
 PosixPath('pathlib-foo/logs/dir1'),
 PosixPath('pathlib-foo/logs/file1'),
 PosixPath('pathlib-foo/logs/file10'),
 PosixPath('pathlib-foo/logs/dir100')]
# et pour les recherches récursives on fait comme ceci
# le **/ va matcher tous les dossiers en dessous de 'logs'

list(logs.glob("**"))
[PosixPath('pathlib-foo/logs'),
 PosixPath('pathlib-foo/logs/file100'),
 PosixPath('pathlib-foo/logs/dir10'),
 PosixPath('pathlib-foo/logs/dir1'),
 PosixPath('pathlib-foo/logs/file1'),
 PosixPath('pathlib-foo/logs/file10'),
 PosixPath('pathlib-foo/logs/dir100'),
 PosixPath('pathlib-foo/logs/dir100/filecxx'),
 PosixPath('pathlib-foo/logs/dir100/filecccxx'),
 PosixPath('pathlib-foo/logs/dir100/fileccxx'),
 PosixPath('pathlib-foo/logs/dir1/filebbbxxxxxx'),
 PosixPath('pathlib-foo/logs/dir1/filebxxxxxx'),
 PosixPath('pathlib-foo/logs/dir1/filebbxxxxxx'),
 PosixPath('pathlib-foo/logs/dir10/fileaaaxxxx'),
 PosixPath('pathlib-foo/logs/dir10/fileaxxxx'),
 PosixPath('pathlib-foo/logs/dir10/fileaaxxxx')]
# du coup pour trouver tous les fichiers on fait

list(logs.glob("**/*"))
[PosixPath('pathlib-foo/logs/file100'),
 PosixPath('pathlib-foo/logs/dir10'),
 PosixPath('pathlib-foo/logs/dir1'),
 PosixPath('pathlib-foo/logs/file1'),
 PosixPath('pathlib-foo/logs/file10'),
 PosixPath('pathlib-foo/logs/dir100'),
 PosixPath('pathlib-foo/logs/dir10/fileaaaxxxx'),
 PosixPath('pathlib-foo/logs/dir10/fileaxxxx'),
 PosixPath('pathlib-foo/logs/dir10/fileaaxxxx'),
 PosixPath('pathlib-foo/logs/dir1/filebbbxxxxxx'),
 PosixPath('pathlib-foo/logs/dir1/filebxxxxxx'),
 PosixPath('pathlib-foo/logs/dir1/filebbxxxxxx'),
 PosixPath('pathlib-foo/logs/dir100/filecxx'),
 PosixPath('pathlib-foo/logs/dir100/filecccxx'),
 PosixPath('pathlib-foo/logs/dir100/fileccxx')]
# pas utilisé dans cet exercice, mais on peut facilement
# faire des calculs du genre de:

path = root / "logs" / "dir100" / "filecxx"

path.parts
('pathlib-foo', 'logs', 'dir100', 'filecxx')
# ou encore

list(path.parents)
[PosixPath('pathlib-foo/logs/dir100'),
 PosixPath('pathlib-foo/logs'),
 PosixPath('pathlib-foo'),
 PosixPath('.')]
# ou encore

path.absolute()
PosixPath('/home/docs/checkouts/readthedocs.org/user_builds/flotpython-exos-python/checkouts/main/notebooks/exos/basic/pathlib-foo/logs/dir100/filecxx')
path.relative_to(root)
PosixPath('logs/dir100/filecxx')
# etc etc..

solution#

variantes possibles#

  • passer en paramètre les extensions de fichier qui sont intéressantes; par exemple on pourrait accepter pour ce paramètre

    • None: le défaut, comme on vient de faire

    • une chaine unique, e.g. "py" pour ne regarder que les *.py

    • une liste d’extensions

  • afficher la première ligne de chaque fichier - pour cela il est sans doute idoine de se définir une nouvelle fonction

  • en faire un script qui puisse se lancer depuis le terminal

  • etc…