Trova un file in Python


110

Ho un file che potrebbe trovarsi in una posizione diversa sulla macchina di ciascun utente. C'è un modo per implementare una ricerca per il file? Un modo in cui posso passare il nome del file e l'albero delle directory in cui cercare?


Vedi il modulo os per os.walk o os.listdir Vedi anche questa domanda stackoverflow.com/questions/229186/… per il codice di esempio
Martin Beckett

Risposte:


251

os.walk è la risposta, questa troverà la prima corrispondenza:

import os

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

E questo troverà tutte le corrispondenze:

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

E questo corrisponderà a uno schema:

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')

2
Nota che questi esempi troveranno solo file, non directory con lo stesso nome. Se vuoi trovare qualsiasi oggetto nella directory con quel nome potresti voler usareif name in file or name in dirs
Mark E. Hamilton

9
Fai attenzione alla distinzione tra maiuscole e minuscole. for name in files:fallirà la ricerca super-photo.jpgquando è super-photo.JPGnel file system. (Un'ora della mia vita mi piacerebbe indietro ;-) Un po 'di soluzione disordinata èif str.lower(name) in [x.lower() for x in files]
matt wilkie

Che ne dici di usare la resa invece di preparare l'elenco dei risultati? ..... if fnmatch.fnmatch (name, pattern): yield os.path.join (root, name)
Berci

Si prega di considerare l'aggiornamento della risposta alle primitive di Python 3.x
Dima Tisnek

1
L'elenco di comprensione può sostituire la funzione, ad esempio find_all: res = [os.path.join (root, name) for root, dirs, files in os.walk (path) if name in files]
Nir

23

Ho usato una versione di os.walke su una directory più grande ho avuto tempi di circa 3,5 sec. Ho provato due soluzioni casuali senza grandi miglioramenti, poi l'ho fatto:

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

Sebbene sia solo POSIX, ho ottenuto 0,25 sec.

Da questo, credo che sia del tutto possibile ottimizzare molto la ricerca completa in modo indipendente dalla piattaforma, ma è qui che ho interrotto la ricerca.


6

Se stai usando Python su Ubuntu e vuoi che funzioni solo su Ubuntu, un modo sostanzialmente più veloce è usare il locateprogramma del terminale come questo.

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_resultsè uno listdei percorsi di file assoluti. Questo è 10.000 volte più veloce dei metodi sopra e per una ricerca che ho fatto è stato ~ 72.000 volte più veloce.


5

In Python 3.4 o versioni successive puoi usare pathlib per eseguire il globbing ricorsivo:

>>> 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')]

Riferimento: https://docs.python.org/3/library/pathlib.html#pathlib.Path.glob

In Python 3.5 o versioni successive puoi anche eseguire il globbing ricorsivo in questo modo:

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

Riferimento: https://docs.python.org/3/library/glob.html#glob.glob



3

Se stai lavorando con Python 2 hai un problema con la ricorsione infinita su Windows causata da collegamenti simbolici autoreferenziali.

Questo script eviterà di seguirli. Nota che questo è specifico di 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

Restituisce un elenco con tutti i percorsi che puntano ai file nell'elenco dei nomi di file. Uso:

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

2

Di seguito usiamo un argomento booleano "first" per passare dalla prima corrispondenza a tutte le corrispondenze (un valore predefinito che è equivalente a "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 

0

La risposta è molto simile a quelle esistenti, ma leggermente ottimizzata.

Quindi puoi trovare qualsiasi file o cartella in base al modello:

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)
    )

sia per sottostringa:

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
    )

o utilizzando un predicato:

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)
    )

per cercare solo file o solo cartelle - sostituire "dirs + files", ad esempio, con solo "dirs" o solo "file", a seconda di ciò che ti serve.

Saluti.


0

La risposta di SARose ha funzionato per me fino a quando non ho aggiornato da Ubuntu 20.04 LTS. La leggera modifica che ho apportato al suo codice lo fa funzionare sull'ultima versione di 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
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.