Найдите файл в Python

110

У меня есть файл, который может находиться в разных местах на каждом компьютере пользователя. Есть ли способ реализовать поиск файла? Как я могу передать имя файла и дерево каталогов для поиска?

направленное действие
источник
См. Модуль os для os.walk или os.listdir См. Также этот вопрос stackoverflow.com/questions/229186/… для примера кода
Мартин Беккет,

Ответы:

251

os.walk - это ответ, это найдет первое совпадение:

import os

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

И это найдет все совпадения:

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

И это будет соответствовать шаблону:

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
Обратите внимание, что эти примеры будут искать только файлы, а не каталоги с тем же именем. Если вы хотите найти в каталоге какой-либо объект с таким именем, вы, возможно, захотите использоватьif name in file or name in dirs
Марк Э. Гамильтон
9
Будьте осторожны с чувствительностью к регистру. for name in files:не удастся найти, super-photo.jpgкогда он находится super-photo.JPGв файловой системе. (Я бы хотел вернуться на час моей жизни ;-) Немного грязное исправлениеif str.lower(name) in [x.lower() for x in files]
Мэтт Уилки
Как насчет использования yield вместо подготовки списка результатов? ..... if fnmatch.fnmatch (name, pattern): yield os.path.join (root, name)
Берчи
Пожалуйста, подумайте об обновлении своего ответа на примитивы Python 3.x
Дима Тиснек
1
Список понимания может заменить функцию, например find_all: res = [os.path.join (root, name) для root, dirs, files in os.walk (path) if name in files]
Nir
23

Я использовал версию os.walkи в более крупном каталоге время около 3,5 секунд. Я попробовал два случайных решения без особого улучшения, а потом просто сделал:

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

Пока это только POSIX, у меня получилось 0,25 сек.

Исходя из этого, я считаю, что вполне возможно оптимизировать весь поиск независимо от платформы, но именно здесь я остановил исследование.

кгадек
источник
6

Если вы используете Python в Ubuntu и хотите, чтобы он работал только в Ubuntu, значительно более быстрым способом является использование такой locateпрограммы терминала .

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является одним listиз абсолютных путей к файлам. Это в 10 000 раз быстрее, чем методы, описанные выше, а один поиск, который я сделал, был в ~ 72 000 раз быстрее.

SARose
источник
5

В Python 3.4 или новее вы можете использовать pathlib для рекурсивного подстановки:

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

Ссылка: https://docs.python.org/3/library/pathlib.html#pathlib.Path.glob

В Python 3.5 или новее вы также можете выполнять рекурсивную глобализацию следующим образом:

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

Ссылка: https://docs.python.org/3/library/glob.html#glob.glob

Кеньон
источник
3

Для быстрого поиска независимо от ОС используйте scandir

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

Прочтите http://bugs.python.org/issue11406, чтобы узнать, почему.

Дима Тиснек
источник
7
В частности, используйте scandir.walk()ответ @ Nadia. Обратите внимание, что если вы используете Python 3.5+, os.walk()у вас scandir.walk()уже есть ускорение. Кроме того, PEP 471 , вероятно, лучший документ для информации, чем этот выпуск.
Бен Хойт
3

Если вы работаете с Python 2, у вас есть проблема с бесконечной рекурсией в окнах, вызванная саморекламными символическими ссылками.

Этот скрипт не будет следовать этим. Обратите внимание, что это зависит от 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

Он возвращает список со всеми путями, которые указывают на файлы в списке имен файлов. Использование:

find("C:\\", ["file1.abc", "file2.abc", "file3.abc", "file4.abc", "file5.abc"])
FMF
источник
2

Ниже мы используем логический аргумент «first» для переключения между первым совпадением и всеми совпадениями (значение по умолчанию эквивалентно «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

Ответ очень похож на существующие, но немного оптимизирован.

Таким образом, вы можете найти любые файлы или папки по шаблону:

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

либо по подстроке:

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
    )

или используя предикат:

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

для поиска только файлов или только папок - замените, например, «каталоги + файлы» только «каталогами» или только «файлами», в зависимости от того, что вам нужно.

С уважением.

Станислав Кузьмич
источник
0

Ответ SARose работал у меня, пока я не обновился с Ubuntu 20.04 LTS. Небольшое изменение, которое я внес в его код, заставляет его работать в последней версии 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
Джастин Тернер
источник