Encontre um arquivo em python

110

Tenho um arquivo que pode estar em um local diferente na máquina de cada usuário. Existe uma maneira de implementar uma pesquisa para o arquivo? Uma forma de passar o nome do arquivo e a árvore de diretórios a pesquisar?

direção
fonte
Veja o módulo de os para os.walk ou os.listdir Veja também esta questão stackoverflow.com/questions/229186/… para código de amostra
Martin Beckett

Respostas:

251

os.walk é a resposta, ele encontrará a primeira correspondência:

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 isso encontrará todas as correspondências:

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 isso vai corresponder a um padrão:

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
fonte
2
Observe que esses exemplos encontrarão apenas arquivos, não diretórios com o mesmo nome. Se você quiser encontrar qualquer objeto no diretório com esse nome, você pode usarif name in file or name in dirs
Mark E. Hamilton
9
Tenha cuidado com a distinção entre maiúsculas e minúsculas. for name in files:falhará ao procurar super-photo.jpgquando está super-photo.JPGno sistema de arquivos. (uma hora da minha vida que eu gostaria de voltar ;-) Solução um pouco complicada éif str.lower(name) in [x.lower() for x in files]
matt wilkie
Que tal usar o rendimento em vez de preparar a lista de resultados? ..... if fnmatch.fnmatch (nome, padrão): rendimento os.path.join (raiz, nome)
Berci
Considere atualizar sua resposta às primitivas do Python 3.x
Dima Tisnek
1
A lista de compreensão pode substituir a função, por exemplo, find_all: res = [os.path.join (root, nome) para root, dirs, arquivos em os.walk (caminho) se o nome nos arquivos]
Nir
23

Usei uma versão de os.walke em um diretório maior, com tempos em torno de 3,5 segundos. Tentei duas soluções aleatórias sem grandes melhorias, mas fiz:

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

Embora seja apenas POSIX, eu tenho 0,25 seg.

A partir disso, acredito que seja inteiramente possível otimizar bastante a busca inteira de forma independente da plataforma, mas foi aí que parei a pesquisa.

Kgadek
fonte
6

Se você está usando Python no Ubuntu e deseja que ele funcione apenas no Ubuntu, uma forma substancialmente mais rápida é usar o locateprograma do terminal desta forma.

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é um listdos caminhos de arquivo absolutos. Isso é 10.000 vezes mais rápido do que os métodos acima e, para uma pesquisa que fiz, foi aproximadamente 72.000 vezes mais rápido.

SARose
fonte
5

No Python 3.4 ou mais recente, você pode usar pathlib para fazer 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')]

Referência: https://docs.python.org/3/library/pathlib.html#pathlib.Path.glob

No Python 3.5 ou mais recente, você também pode fazer globbing recursivo como este:

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

Referência: https://docs.python.org/3/library/glob.html#glob.glob

Kenyon
fonte
3

Para uma pesquisa rápida e independente do sistema operacional, use scandir

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

Leia http://bugs.python.org/issue11406 para obter detalhes sobre o motivo.

Dima Tisnek
fonte
7
Especificamente, use scandir.walk()a resposta de @Nadia. Observe que se você estiver usando o Python 3.5+, os.walk()já tem os scandir.walk()aceleradores. Além disso, o PEP 471 é provavelmente um documento melhor para ler informações do que esse problema.
Ben Hoyt
3

Se você estiver trabalhando com Python 2, terá um problema com a recursão infinita em janelas causada por links simbólicos autorreferidos.

Este script evitará seguir aqueles. Observe que isso é específico do 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

Ele retorna uma lista com todos os caminhos que apontam para arquivos na lista de nomes de arquivos. Uso:

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

A seguir, usamos um argumento booleano "primeiro" para alternar entre a primeira correspondência e todas as correspondências (um padrão que é 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 
Leon Chang
fonte
0

A resposta é muito semelhante às existentes, mas ligeiramente otimizada.

Assim, você pode encontrar qualquer arquivo ou pasta por padrão:

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

quer por substring:

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 usando um 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 pesquisar apenas arquivos ou apenas pastas - substitua “dirs + arquivos”, por exemplo, por apenas “dirs” ou apenas “arquivos”, dependendo do que você precisa.

Saudações.

Stanislav Kuzmich
fonte
0

A resposta de SARose funcionou para mim até que eu atualizei do Ubuntu 20.04 LTS. A ligeira mudança que fiz em seu código o faz funcionar na versão mais recente do 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
fonte