Rechercher un fichier en python

110

J'ai un fichier qui peut se trouver à un endroit différent sur la machine de chaque utilisateur. Existe-t-il un moyen de mettre en œuvre une recherche pour le fichier? Un moyen de transmettre le nom du fichier et l'arborescence de répertoires à rechercher?

mise en scène
la source
Voir le module os pour os.walk ou os.listdir Voir aussi cette question stackoverflow.com/questions/229186/… pour un exemple de code
Martin Beckett

Réponses:

251

os.walk est la réponse, cela trouvera la première correspondance:

import os

def find(name, path):
    for root, dirs, files in os.walk(path):
        if name in files:
            return os.path.join(root, name)

Et cela trouvera tous les matchs:

def find_all(name, path):
    result = []
    for root, dirs, files in os.walk(path):
        if name in files:
            result.append(os.path.join(root, name))
    return result

Et cela correspondra à un modèle:

import os, fnmatch
def find(pattern, path):
    result = []
    for root, dirs, files in os.walk(path):
        for name in files:
            if fnmatch.fnmatch(name, pattern):
                result.append(os.path.join(root, name))
    return result

find('*.txt', '/path/to/dir')
Nadia Alramli
la source
2
Notez que ces exemples ne trouveront que des fichiers, pas des répertoires avec le même nom. Si vous voulez trouver un objet dans le répertoire avec ce nom, vous voudrez peut-être utiliserif name in file or name in dirs
Mark E. Hamilton
9
Faites attention à la sensibilité à la casse. for name in files:échouera à la recherche super-photo.jpglorsqu'il est super-photo.JPGdans le système de fichiers. (une heure de ma vie, j'aimerais revenir ;-) Une solution un peu désordonnée estif str.lower(name) in [x.lower() for x in files]
matt wilkie
Pourquoi ne pas utiliser yield au lieu de préparer la liste de résultats? ..... if fnmatch.fnmatch (nom, modèle): yield os.path.join (racine, nom)
Berci
Veuillez envisager de mettre à jour votre réponse aux primitives Python 3.x
Dima Tisnek
1
La liste de compréhension peut remplacer la fonction, par exemple find_all: res = [os.path.join (racine, nom) pour root, dirs, fichiers dans os.walk (chemin) si nom dans les fichiers]
Nir
23

J'ai utilisé une version de os.walket sur un répertoire plus grand, j'ai obtenu des durées d'environ 3,5 secondes. J'ai essayé deux solutions aléatoires sans grande amélioration, puis je l'ai fait:

paths = [line[2:] for line in subprocess.check_output("find . -iname '*.txt'", shell=True).splitlines()]

Bien que ce soit uniquement POSIX, j'ai 0,25 s.

À partir de là, je pense qu'il est tout à fait possible d'optimiser beaucoup la recherche entière d'une manière indépendante de la plate-forme, mais c'est là que j'ai arrêté la recherche.

kgadek
la source
6

Si vous utilisez Python sur Ubuntu et que vous souhaitez uniquement qu'il fonctionne sur Ubuntu, un moyen beaucoup plus rapide est d'utiliser le locateprogramme du terminal comme celui-ci.

import subprocess

def find_files(file_name):
    command = ['locate', file_name]

    output = subprocess.Popen(command, stdout=subprocess.PIPE).communicate()[0]
    output = output.decode()

    search_results = output.split('\n')

    return search_results

search_resultsest l'un listdes chemins d'accès absolus aux fichiers. C'est 10 000 fois plus rapide que les méthodes ci-dessus et pour une recherche que j'ai effectuée, c'était environ 72 000 fois plus rapide.

SARose
la source
5

Dans Python 3.4 ou plus récent, vous pouvez utiliser pathlib pour effectuer une globalisation récursive:

>>> import pathlib
>>> sorted(pathlib.Path('.').glob('**/*.py'))
[PosixPath('build/lib/pathlib.py'),
 PosixPath('docs/conf.py'),
 PosixPath('pathlib.py'),
 PosixPath('setup.py'),
 PosixPath('test_pathlib.py')]

Référence: https://docs.python.org/3/library/pathlib.html#pathlib.Path.glob

Dans Python 3.5 ou plus récent, vous pouvez également effectuer une globalisation récursive comme ceci:

>>> import glob
>>> glob.glob('**/*.txt', recursive=True)
['2.txt', 'sub/3.txt']

Référence: https://docs.python.org/3/library/glob.html#glob.glob

Kenyon
la source
3

Pour une recherche rapide et indépendante du système d'exploitation, utilisez scandir

https://github.com/benhoyt/scandir/#readme

Lisez http://bugs.python.org/issue11406 pour savoir pourquoi.

Dima Tisnek
la source
7
Plus précisément, utilisez scandir.walk()la réponse de @ Nadia. Notez que si vous utilisez Python 3.5+, os.walk()les scandir.walk()accélérations sont déjà disponibles. En outre, PEP 471 est probablement un meilleur document à lire pour plus d'informations que ce problème.
Ben Hoyt
3

Si vous travaillez avec Python 2, vous rencontrez un problème de récursivité infinie sur les fenêtres causé par des liens symboliques auto-référencés.

Ce script évitera de suivre ceux-ci. Notez que cela est spécifique à Windows !

import os
from scandir import scandir
import ctypes

def is_sym_link(path):
    # http://stackoverflow.com/a/35915819
    FILE_ATTRIBUTE_REPARSE_POINT = 0x0400
    return os.path.isdir(path) and (ctypes.windll.kernel32.GetFileAttributesW(unicode(path)) & FILE_ATTRIBUTE_REPARSE_POINT)

def find(base, filenames):
    hits = []

    def find_in_dir_subdir(direc):
        content = scandir(direc)
        for entry in content:
            if entry.name in filenames:
                hits.append(os.path.join(direc, entry.name))

            elif entry.is_dir() and not is_sym_link(os.path.join(direc, entry.name)):
                try:
                    find_in_dir_subdir(os.path.join(direc, entry.name))
                except UnicodeDecodeError:
                    print "Could not resolve " + os.path.join(direc, entry.name)
                    continue

    if not os.path.exists(base):
        return
    else:
        find_in_dir_subdir(base)

    return hits

Il renvoie une liste avec tous les chemins qui pointent vers des fichiers dans la liste des noms de fichiers. Usage:

find("C:\\", ["file1.abc", "file2.abc", "file3.abc", "file4.abc", "file5.abc"])
FMF
la source
2

Ci-dessous, nous utilisons un argument booléen "first" pour basculer entre la première correspondance et toutes les correspondances (valeur par défaut équivalente à "find. -Name file"):

import  os

def find(root, file, first=False):
    for d, subD, f in os.walk(root):
        if file in f:
            print("{0} : {1}".format(file, d))
            if first == True:
                break 
Léon Chang
la source
0

La réponse est très similaire à celles existantes, mais légèrement optimisée.

Ainsi, vous pouvez trouver tous les fichiers ou dossiers par modèle:

def iter_all(pattern, path):
    return (
        os.path.join(root, entry)
        for root, dirs, files in os.walk(path)
        for entry in dirs + files
        if pattern.match(entry)
    )

soit par sous-chaîne:

def iter_all(substring, path):
    return (
        os.path.join(root, entry)
        for root, dirs, files in os.walk(path)
        for entry in dirs + files
        if substring in entry
    )

ou en utilisant un prédicat:

def iter_all(predicate, path):
    return (
        os.path.join(root, entry)
        for root, dirs, files in os.walk(path)
        for entry in dirs + files
        if predicate(entry)
    )

pour rechercher uniquement des fichiers ou uniquement des dossiers - remplacez par exemple «dirs + files» par uniquement «dirs» ou seulement «files», selon ce dont vous avez besoin.

Cordialement.

Stanislav Kuzmich
la source
0

La réponse de SARose a fonctionné pour moi jusqu'à ce que je mette à jour à partir d'Ubuntu 20.04 LTS. Le léger changement que j'ai apporté à son code le fait fonctionner sur la dernière version d'Ubuntu.

import subprocess

def find_files(file_name):
    file_name = 'chromedriver'
    command = ['locate'+ ' ' + file_name]
    output = subprocess.Popen(command, stdout=subprocess.PIPE, shell=True).communicate()[0]
    output = output.decode()
    search_results = output.split('\n')
    return search_results
Justin Turner
la source