Python listing recursif
Différentes façons de recenser les éléments d'une arborescence.
Introduction
Dans mon apprentissage de Python, j'ai souvent cherché à savoir faire en Python ce que je sais faire dans d'autres langages aussi bien interprétés que compilés. Le but n'est pas de tomber dans l'extremisme pro-Python, mais d'être cultivé. De toutes façons, les solutions évoquées ici ne sont pas forcément optimales et de plus, ne sont pas toujours portables. J'utilise Linux et FreeBSD et une grande partie de mes scripts ne fonctionneront pas sous Windows. Dans ce document, nous allons traiter la façon de faire un listing de contenu avec une profondeur non prédéterminée.C'est quoi?
Je suis responsable d'une webTV communautaire qui diffuse des emission à partir de fichiers vidéos (au format ogg/Theora). Ces fichiers vidéos sont apportés par les membres de l'équipe par FTP. Mais pour savoir combien de fichier vidéo j'ai au total, il me faut les lister (juste tout lister, pas trier).Cependant, chaque membre a sa façon d'organiser ses répertoires et d'un membre à l'autre on retrouve des gens qui mettent tout dans un seul répertoire, et d'autres, plus ordonnés qui ont pris la peine de trier dans des répertoires. Mais il peut y avoir beaucoup de sous-répertoires en fonction du zèle du membre d'équipe.
Glob
Lorsque je me suis documenté, il m'est apparu que glob pouvait me satisfaire.En effet,à partir du répertoire courant, si on veut connaître tous les fichiers qui se terminent par “.ogg”, alors il suffit de:
import globOn appelera (uniquement dans ce document) cette recherche “de profondeur 0”, car elle n'a necessité ni effectué de recherche dans aucun sous répertoire du répertoire courant.
glob.glob(“*.ogg”)
Si je veux faire une recherche “de profondeur 1”, alors la syntaxe serait:
glob.glob(“*/*.ogg”)Cela fera la recherche uniquement dans les sous-répertoires directs du répertoire courant (et pas dans les sous-sous répertoires). Cette méthode est donc efficace si on connait à l'avance la profondeur de recherche qu'on souhaite effectuer. Ce n'est pas notre cas.
os.walk()
Comme glob ne nous a pas convenu, partons donc à la recherche d'autres outils. Un coup de documentation par-ci par-là et voici que os.walk() semble répondre à ce besoin. La lecture de la documentation d'os.walk() et des exemples fournis sont très franchement décourageant au début. Cependant, avec du recul, c'est un outil surpuissant. Nous allons prendre un moment pour décortiquer son fonctionnement. Pour utiliser os.walk(), il faut d'abord charger le module os:import osIl est plus prudent de définir aussi la racine à partir de laquelle on veut lister les choses.
ma_racine=”/home/mihamina”Il faut ensuite se dire que os.walk() retourne une séquence de 3-uplets (en Français correct on dit triplets). Pour être plus clair, on utilise os.walk() comme ceci:
for (a,b,c) in os.walk(ma_racine):Nous allons ordonner notre étude d'os.walk() en étudiant un à un chaque composante dudit triplet. En reprenant notre représentation imagée ci-dessus, nous allons voir le contenu de “a”, puis celui ce “b” et enfin celui de “c”.
[...]
Chemin scanné et courant
Pour faire court, voyons directement ce que donne ce code:for (a,b,c) in os.walk(ma_racine):On constate que c'est la liste de tous les répertoires et sous-répertoires qui existent sous “/home/mihamina” (ou ce que vous avez mis comme chemin dans “ma_racine”). Pour des raisons de clarté nous allons refaire la manipulation en renommant plus explicitement les variables:
print a
for (chemin,b,c) in os.walk(ma_racine):Est-ce que jusque là tout est clair?
print chemin
On constate que l'ordre de parcours de l'arborescence est à priori désordonnée, mais en fait elle ne l'est pas, il est tout simplement différent de l'ordre de parcours des shells courants. On constate aussi que chemin n'est qu'une liste de répertoires, pas de fichiers. Or un répertoire peut contenir des fichiers et des répertoires.
Répertoires contenus dans “chemin”
Maintenant nous allons voir ce que contient “b”. Dans chaque triplet (chemin, b, c), b contient la liste des sous-répertoire directs de "chemin".Par direct, nous convenons de désigner uniquement les sous-répertoires (niveau 1) et exclure les sous-sous-répertoires, sous-sous-sous-...-répertoires (niveau > 1). Donc, notre exemple peut s'écrire ainsi:
for (chemin,b,c) in os.walk(ma_racine):Pour plus de clarté, nous allons donc réécrire notre exemple:
print “Dans “+chemin+” il y a les repertoires:”
for rep in b:
print “\t”+ rep
for (chemin,reps,c) in os.walk(ma_racine):Dans l'exemple ci-dessus, nous avons constaté qu'il ne listait que les répertoires. Et les fichiers contenu dans le répertoire alors?
print “Dans “+chemin+” il y a:”
for rep in reps:
print “- Le repertoire\t”+ rep
Fichiers contenus dans “chemin”
C'est dans “c” que se trouve la liste des fichiers contenus dans “chemin”. Nous pouvons nous inspirer du dernier exemple:for (chemin,reps,c) in os.walk(ma_racine):Nous allons donc faire des modification cosmétiques de notre exemple:
print “Dans “+chemin+” il y a:”
for fichier in c:
print “- Le fichier\t”+ fichier
for (chemin,reps,fichiers) in os.walk(ma_racine):Nous avons maintenant toutes les cartes en main pour exploiter un minimum os.walk().
print “Dans “+chemin+” il y a:”
for fichier in fichiers:
print “- Le fichier\t”+ fichier
Et la recherche de fichiers?
Pour rechercher tous les fichiers sous une arborescences, nous pouvons donc faire avec os.walk():for (chemin,reps,fichiers) in os.walk(ma_racine):Si nous préférons que tout soit stocké dans une liste, nous pouvons faire:
for fichier in fichiers:
print chemin+fichier
listing=[]Le souci c'est que tel quel, il va nous extraire tout ce qu'il trouve, alors que nous ne voulons que les fichiers *.ogg. Pour cela nous devons mettre une condition, par exemple sur/avec une regexp, mais il y a d'autres façons. Donc avec les regexp:
for (chemin,reps,fichiers) in os.walk(ma_racine):
for fichier in fichiers:
listing.append(chemin+fichier)
import re
fich_ogg=re.compile('.+\.ogg$')
listing=[]
for (chemin,reps,fichiers) in os.walk(ma_racine):
for fichier in fichiers:
if fich_ogg.match(fichier):
listing.append(chemin+'/'+fichier)
os.path.walk()
La solution qui m'a paru la plus adaptée, de par sa conception et par la syntaxe qu'il impose est os.path.walk().Cette fonction prend trois arguments dont le premier est la racine de l'arborescence à scanner, le deuxième est une fonction, et le troisième est un argument “de travail” (qui peut être None si on ne désire pas s'en servir).
C'est le deuxième argument d'os.path.walk() qui nous interesse. Tout comme os.walk(), os.path.walk() va déscendre comme un grand dans toute l'arboresence. Il va donc avoir sa propre liste de “chemin” (les chemins de l'exemple précédent). Par contre, il ne va pas séparer dans deux listes les répetoires et les fichiers contenus dans les chemins. De plus, à chaque “chemin” il va appeler automtiquement une fonction.
La fonction qu'il appelle automatiquement, elle n'est pas prédéfinie! C'est là le chic de la chose. Nous allons donc pouvoir définir une fonction qui ajoute ou n'ajoute pas à notre listing. Pour faire simple, prenons une fonction:
def ajoute_si_ogg(une_liste,chemin,liste_noms):Soient une liste vide, un chemin et une liste de fichiers contenus dans ce chemin.
for nom in liste_noms:
if nom.endswith(".ogg"):
une_liste.append(os.path.join(chemin,nom))
return None
listing=[]Appeler ajoute_si_ogg() avec ces arguments donne:
un_chemin=”/home/mihamina”
une_liste_de_fichiers_dans_un_chemin=
[ “toto.ppt”,
“titi.ogg”,
“tata.ogg”,
“tete.pps”
]
ajoute_si_ogg(listing,un_chemin,une_liste_de_fichiers_dans_un_chemin)Dans listing on ne verra plus que des fichiers .ogg.On peut maintenant appeler os.path.walk():
listing=[]On n'aura plus que les fichiers .ogg qui nous interessent dans listing.
os.path.walk(un_chemin,ajoute_si_ogg,listing)