Python find directory: системное администрирование файловых каталогов
Основные методы поиска директорий в Python
Как найти все папки с заданным именем рекурсивно, используя современный подход?
Наиболее эффективное и рекомендуемое решение - модуль pathlib с методом rglob(). Он возвращает объекты Path, поддерживает фильтрацию по имени и работает одинаково на всех платформах.
from pathlib import Path
def find_directories_by_name(root_path, target_name):
root = Path(root_path)
if not root.is_dir():
return []
# rglob("*") находит все, затем фильтруем по is_dir() и имени
return [p for p in root.rglob("*") if p.is_dir() and p.name == target_name]
# Пример использования
result = find_directories_by_name("/home/user", "logs")
for d in result:
print(d)путь к path python (путь к python (переменная path))
Пояснение: Path.rglob("*") обходит всё дерево рекурсивно. Фильтр p.is_dir() оставляет только каталоги, а сравнение имени решает задачу поиска. Метод безопасен для путей с пробелами и не зависит от локали.
Возможные проблемы и их решения:
- PermissionError - при отсутствии прав доступа к некоторым папкам. Обработка исключения: обернуть в try/except или использовать Path.is_dir() с проверкой доступа.
- Символические ссылки - rglob по умолчанию следует за ними, что может привести к бесконечным циклам. Для предотвращения используйте Path.resolve(strict=False) или проверяйте, не пройдена ли папка ранее.
- Большое количество файлов - rglob загружает всё дерево в память. Альтернатива - итератор Path.glob("**/target_name") с условием, что target_name - точное имя.
Как найти директории, используя классический os.walk?
Модуль os с функцией walk() даёт полный контроль над обходом и позволяет обрабатывать ошибки доступа через параметр onerror.
import os
def find_dirs_os_walk(root_path, target_name):
matches = []
for dirpath, dirnames, filenames in os.walk(root_path):
for d in dirnames:
if d == target_name:
matches.append(os.path.join(dirpath, d))
return matches
print(find_dirs_os_walk("/var", "cache"))Python pathlib path (использование pathlib в python)
Проблемы:
- Ошибки доступа - os.walk по умолчанию пропускает недоступные папки, но не сообщает об этом. Для логирования используйте параметр onerror.
- Производительность - os.walk создаёт списки всех каталогов и файлов на каждом уровне, что может быть затратно при глубокой вложенности.
Как выполнить поиск с помощью glob и fnmatch?
Модуль glob поддерживает шаблоны с подстановочными знаками (*, ?, [abc]). Для рекурсивного поиска используйте ** (Python 3.5+).
import glob
def find_dirs_glob(pattern):
# pattern может быть, например, "**/logs"
return [p for p in glob.glob(pattern, recursive=True) if os.path.isdir(p)]
# Пример
print(find_dirs_glob("/home/**/logs"))
Path python 2 (путь к python 2)
Пояснение: glob.glob с recursive=True обходит все поддиректории. Ограничение - нельзя задать глубину поиска. Для фильтрации по типу объекта приходится дополнительно вызывать os.path.isdir.
Типичная ошибка:
Если в шаблоне не указать **, поиск будет только в текущей папке. Также glob не обнаруживает символические ссылки на каталоги по умолчанию.
Как воспользоваться утилитой find из командной строки через subprocess?
Для системных администраторов иногда проще вызвать системную команду find, особенно когда нужны дополнительные критерии (размер, время, тип).
import subprocess
def find_with_shell(root, name):
cmd = ["find", root, "-type", "d", "-name", name]
result = subprocess.run(cmd, capture_output=True, text=True, check=False)
if result.returncode != 0:
# Обработка ошибок
return []
return result.stdout.strip().split('\n') if result.stdout else []
print(find_with_shell("/etc", "conf.d"))Python site packages path (путь к site-packages в python)
Данный подход использует системный find, который очень быстр и гибок. Однако он не кроссплатформенный (нет в Windows).
Проблемы:
- Зависимость от ОС - команда может отсутствовать или иметь другой синтаксис.
- Безопасность - при передаче пользовательского ввода в shell необходимо экранировать (использовать список аргументов).
- Кодировка вывода - в Windows или при нестандартных локальных настройках возможны ошибки декодирования.
Как искать директории по содержимому файлов внутри них?
Иногда требуется найти папки, в которых есть файл с определённым содержимым. Комбинируем поиск каталогов и проверку содержимого.
from pathlib import Path
def find_dirs_containing_text(root, filename, search_text):
result = []
for dir_path in Path(root).rglob("*"):
if dir_path.is_dir():
target_file = dir_path / filename
if target_file.exists() and target_file.is_file():
try:
content = target_file.read_text(encoding='utf-8', errors='ignore')
if search_text in content:
result.append(str(dir_path))
except Exception:
pass
return result
# Пример
print(find_dirs_containing_text("/var/log", "error.log", "CRITICAL"))Сложность:
При большом количестве файлов чтение каждого снижает производительность. Рекомендуется сначала отфильтровать каталоги по имени, а затем проверять содержимое.
Расширенные примеры поиска директорий с пояснениями
1. Поиск с ограничением глубины рекурсии
Иногда нужно искать только на определённой глубине, например, не глубже 3 уровней. Реализация на pathlib:
from pathlib import Path
def find_dirs_depth_limit(root_path, target_name, max_depth):
root = Path(root_path)
result = []
for p in root.rglob("*"):
# относительный путь от root, считаем глубину по количеству разделителей
rel = p.relative_to(root)
depth = len(rel.parts)
if depth > max_depth:
continue
if p.is_dir() and p.name == target_name:
result.append(p)
return result
print(find_dirs_depth_limit("/tmp", "data", 2))[PosixPath('/tmp/data'), PosixPath('/tmp/sub/data')] # не глубже 22. Поиск с исключением скрытых каталогов (начинающихся с точки)
В Unix-системах скрытые папки начинаются с точки. Их можно исключить:
from pathlib import Path
def find_visible_dirs(root, name):
result = []
for p in Path(root).rglob("*"):
# Проверяем каждый компонент пути на скрытость
if any(part.startswith('.') for part in p.parts):
continue
if p.is_dir() and p.name == name:
result.append(p)
return result
print(find_visible_dirs("/home/user", "projects"))[PosixPath('/home/user/projects')]3. Поиск директорий с наибольшим размером (через os.walk)
Иногда нужно найти самые «тяжёлые» каталоги. Собираем размер всех папок рекурсивно:
import os
def get_directory_size(path):
total = 0
for dirpath, dirnames, filenames in os.walk(path):
for f in filenames:
fp = os.path.join(dirpath, f)
try:
total += os.path.getsize(fp)
except OSError:
pass
return total
def largest_dirs(root, top_n=5):
dirs = []
for entry in os.scandir(root):
if entry.is_dir():
try:
size = get_directory_size(entry.path)
dirs.append((entry.path, size))
except PermissionError:
continue
dirs.sort(key=lambda x: x[1], reverse=True)
return dirs[:top_n]
for path, size in largest_dirs("/var", 3):
print(f"{path}: {size / (1024**3):.2f} GB")/var/log: 1.23 GB /var/cache: 0.87 GB /var/lib: 0.45 GB
4. Параллельный поиск с использованием ThreadPoolExecutor
Для ускорения на многоядерных системах можно распараллелить обход корневых поддиректорий:
from concurrent.futures import ThreadPoolExecutor, as_completed
from pathlib import Path
def search_in_subdir(subdir, target_name):
return [p for p in subdir.rglob("*") if p.is_dir() and p.name == target_name]
def parallel_find(root, target_name, max_workers=4):
root_path = Path(root)
subdirs = [d for d in root_path.iterdir() if d.is_dir()]
results = []
with ThreadPoolExecutor(max_workers=max_workers) as executor:
futures = {executor.submit(search_in_subdir, d, target_name): d for d in subdirs}
for future in as_completed(futures):
results.extend(future.result())
return results
print(parallel_find("/home", "backup"))Важно: при параллельном поиске файловая система может стать узким местом, поэтому прирост производительности не всегда значителен.
5. Поиск директорий с помощью os.scandir и ручной рекурсии
Использование os.scandir даёт более эффективный обход, чем os.walk, поскольку не загружает сразу все имена:
import os
def find_dirs_scandir_recursive(root, target_name):
result = []
try:
with os.scandir(root) as it:
for entry in it:
if entry.is_dir(follow_symlinks=False):
if entry.name == target_name:
result.append(entry.path)
# рекурсивный вызов
result.extend(find_dirs_scandir_recursive(entry.path, target_name))
except PermissionError:
pass
return result
print(find_dirs_scandir_recursive("/opt", "config"))['/opt/app/config', '/opt/app2/config']
6. Поиск с учётом времени последнего изменения (mtime)
Найти папки, изменённые за последние N дней:
from pathlib import Path
import time
def find_dirs_modified_recently(root, days):
cutoff = time.time() - days * 86400
result = []
for p in Path(root).rglob("*"):
if p.is_dir():
stat = p.stat()
if stat.st_mtime > cutoff:
result.append(p)
return result
print(find_dirs_modified_recently("/tmp", 7))Обратите внимание: изменение самого каталога (добавление/удаление файлов) обновляет mtime папки не всегда, поэтому лучше дополнительно проверять содержимое.
7. Поиск и вывод полного пути с форматированием
Вывод результатов в удобном виде для системного администратора:
from pathlib import Path
def find_and_format(root, name):
found = [str(p) for p in Path(root).rglob("*") if p.is_dir() and p.name == name]
if not found:
return "Ничего не найдено"
lines = ["Найденные директории:"]
for i, path in enumerate(found, 1):
lines.append(f"{i}. {path}")
return '\n'.join(lines)
print(find_and_format("/home", "logs"))Найденные директории: 1. /home/user/logs 2. /home/user/project/logs