Encuentra un archivo en python

110

Tengo un archivo que puede estar en un lugar diferente en la máquina de cada usuario. ¿Hay alguna forma de implementar una búsqueda del archivo? ¿Una forma en la que puedo pasar el nombre del archivo y el árbol de directorios para buscar?

dirección
fuente
Consulte el módulo del sistema operativo para os.walk u os.listdir Consulte también esta pregunta stackoverflow.com/questions/229186/… para obtener un código de muestra
Martin Beckett

Respuestas:

251

os.walk es la respuesta, esta encontrará la primera coincidencia:

import os

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

Y esto encontrará todas las coincidencias:

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

Y esto coincidirá con un patrón:

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
fuente
2
Tenga en cuenta que estos ejemplos solo encontrarán archivos, no directorios con el mismo nombre. Si desea encontrar cualquier objeto en el directorio con ese nombre, es posible que desee utilizarif name in file or name in dirs
Mark E. Hamilton
9
Tenga cuidado con la distinción entre mayúsculas y minúsculas. for name in files:fallará al buscar super-photo.jpgcuando esté super-photo.JPGen el sistema de archivos. (una hora de mi vida que me gustaría recuperar ;-) La solución un poco desordenada esif str.lower(name) in [x.lower() for x in files]
Matt Wilkie
¿Qué hay de usar el rendimiento en lugar de preparar la lista de resultados? ..... si fnmatch.fnmatch (nombre, patrón): rendimiento os.path.join (raíz, nombre)
Berci
Considere actualizar su respuesta a las primitivas de Python 3.x
Dima Tisnek
1
La lista de comprensión puede reemplazar la función, por ejemplo, find_all: res = [os.path.join (root, name) for root, dirs, files in os.walk (path) if name in files]
Nir
23

Usé una versión de os.walky en un directorio más grande, obtuve tiempos de alrededor de 3,5 segundos. Probé dos soluciones aleatorias sin gran mejora, luego lo hice:

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

Si bien es solo POSIX, obtuve 0.25 seg.

A partir de esto, creo que es totalmente posible optimizar mucho la búsqueda completa de una manera independiente de la plataforma, pero aquí es donde detuve la investigación.

kgadek
fuente
6

Si está usando Python en Ubuntu y solo quiere que funcione en Ubuntu de una manera sustancialmente más rápida, use el locateprograma del terminal de esta manera.

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_resultses una listde las rutas de archivo absolutas. Esto es 10,000 veces más rápido que los métodos anteriores y para una búsqueda que hice fue ~ 72,000 veces más rápido.

SARose
fuente
5

En Python 3.4 o más reciente, puede usar pathlib para hacer globbing recursivo:

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

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

En Python 3.5 o más reciente, también puede hacer globbing recursivo como este:

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

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

Kenyon
fuente
3

Para una búsqueda rápida e independiente del sistema operativo, utilice scandir

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

Lea http://bugs.python.org/issue11406 para obtener detalles sobre el motivo.

Dima Tisnek
fuente
7
Específicamente, use scandir.walk()la respuesta de @ Nadia. Tenga en cuenta que si está utilizando Python 3.5+, ya os.walk()tiene las scandir.walk()aceleraciones. Además, PEP 471 es probablemente un mejor documento para leer en busca de información que ese problema.
Ben Hoyt
3

Si está trabajando con Python 2, tiene un problema con la recursividad infinita en las ventanas causada por enlaces simbólicos autorreferentes.

Este script evitará seguir esos. Tenga en cuenta que esto es específico de 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

Devuelve una lista con todas las rutas que apuntan a archivos en la lista de nombres de archivo. Uso:

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

A continuación, usamos un argumento booleano "primer" para cambiar entre la primera coincidencia y todas las coincidencias (un valor predeterminado que es equivalente a "buscar archivo de nombre"):

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 
Leon Chang
fuente
0

La respuesta es muy similar a las existentes, pero ligeramente optimizada.

Para que pueda encontrar cualquier archivo o carpeta por patrón:

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

ya sea por subcadena:

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 usando un predicado:

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

para buscar solo archivos o solo carpetas, reemplace "dirs + archivos", por ejemplo, con solo "dirs" o solo "archivos", según lo que necesite.

Saludos.

Stanislav Kuzmich
fuente
0

La respuesta de SARose funcionó para mí hasta que actualicé desde Ubuntu 20.04 LTS. El pequeño cambio que hice en su código lo hace funcionar en la última versión de 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
fuente