Пути к файлам: абсолютные и относительные, . и .., работа в коде

Пути к файлам: абсолютные и относительные, . и .., работа в коде Полезное

Путь к файлу — это строка, которая указывает, где в файловой системе лежит файл или каталог: цепочка имен каталогов, разделенных специальным символом, и в конце имя объекта. Абсолютный путь начинается от корня файловой системы (или от диска в Windows) и не зависит от того, где вы находитесь. Относительный путь отсчитывается от текущего рабочего каталога и без него не имеет однозначного смысла.

Ниже разберем, как устроены пути в Windows и в Unix-подобных системах (Linux, macOS), что означают . и .., как собирать пути в Python через pathlib и как не пропустить чужой путь за пределы нужного каталога.

Мини-словарь

Термин Что это Пример
Корень Начальная точка дерева каталогов / в Linux и macOS, C:\ в Windows
Разделитель Символ между именами в пути / в Unix, \ в Windows (/ Windows тоже обычно принимает)
Текущий рабочий каталог Каталог, от которого считаются относительные пути у процесса вывод pwd в терминале
. Текущий каталог ./run.sh
.. Родительский каталог, на уровень выше ../images/logo.png

Важная поправка к популярной ошибке: переход на уровень вверх — это две точки .., а не многоточие. Имя из трех точек ... в обычных файловых системах ничего специального не означает.

Абсолютный и относительный путь

Возьмем одну структуру каталогов на диске D в Windows:

D:\Data
├── Maps
│   └── Posters
│       └── city.png
└── Shapefiles
    ├── Landuse
    └── Soils

Абсолютный путь к картинке — D:\Data\Maps\Posters\city.png: диск, затем все каталоги по порядку, затем имя файла. Он одинаково работает из любого места.

Если текущий каталог — D:\Data\Shapefiles\Soils, то в соседний Landuse ведет относительный путь ..\Landuse: .. поднимает в Shapefiles, дальше спускаемся в Landuse. Итог — D:\Data\Shapefiles\Landuse. Тот же путь ..\Landuse, запущенный из D:\Data\Maps, приведет уже в несуществующий D:\Data\Landuse.

Признак Абсолютный путь Относительный путь
Откуда отсчет От корня или диска От текущего рабочего каталога
Зависит от места запуска Нет Да
Переносимость проекта на другую машину Плохая: жестко зашит чужой каталог Хорошая, если считать от известной точки
Где уместен Конфиги сервисов, cron, системные команды Ссылки внутри проекта, HTML, импорты ресурсов

Правило выбора: внутри проекта используйте относительные пути, но считайте их от известной точки (корня проекта или файла скрипта), а не от случайного текущего каталога. Абсолютный путь нужен там, где текущий каталог заранее неизвестен: в планировщиках задач и службах.

Windows и Unix: в чем реальная разница

Windows Linux, macOS
Корень Буква диска: C:\, D:\ Один корень /, диски монтируются в каталоги
Разделитель \ (обычно работает и /) /
Регистр имен Как правило не различается (File.txt = file.txt) В Linux различается; в macOS по умолчанию нет
Сетевые пути UNC: \\server\share\file.txt Сетевой ресурс монтируется в каталог
Домашний каталог C:\Users\anna /home/anna, в macOS /Users/anna

Отличий больше, чем разделитель. В Windows есть два неочевидных вида путей. \Data\Maps — путь от корня текущего диска: диска в нем нет, поэтому это не полностью абсолютный путь. D:Maps — путь относительно текущего каталога на диске D. В macOS внешние диски подключаются в /Volumes, в Linux — обычно в /mnt или /media.

Длина пути тоже ограничена по-разному. В Windows классический лимит MAX_PATH — 260 символов; длинные пути можно включить, но только если их поддерживает и система, и конкретная программа.

Пути в коде: pathlib в Python

Склеивать пути строками ("data" + "\\" + name) — источник ошибок и непереносимого кода. В Python для этого есть модуль pathlib. Минимальный полный пример, который одинаково работает на любой ОС, потому что использует «чистые» классы путей без обращения к диску:

from pathlib import PurePosixPath, PureWindowsPath

p = PurePosixPath("/home/anna/projects/report") / "data.csv"
print(p)                # /home/anna/projects/report/data.csv
print(p.parent)         # /home/anna/projects/report
print(p.name, p.stem, p.suffix)  # data.csv data .csv
print(p.is_absolute())  # True

w = PureWindowsPath(r"D:\Data\Maps\Posters\city.png")
print(w.drive)          # D:
print(w.parts)
print(PureWindowsPath(r"\Data\Maps").is_absolute())  # False

Скрипт напечатает путь, склеенный оператором /, родительский каталог, имя, основу и расширение, затем True. Для Windows-пути — диск D:, кортеж частей ('D:\\', 'Data', 'Maps', 'Posters', 'city.png') и False: путь без буквы диска pathlib абсолютным не считает.

В реальной программе берут просто Path: он сам выбирает правила текущей ОС и умеет работать с диском (exists(), read_text(), mkdir()).

Как вычисляется .., тоже стоит различать. os.path.normpath схлопывает .. чисто по тексту, а Path.resolve() смотрит на реальную файловую систему и раскрывает символические ссылки, поэтому результаты могут разойтись:

import posixpath

print(posixpath.normpath("/data/shapefiles/soils/../landuse"))  # /data/shapefiles/landuse
print(posixpath.normpath("./reports/./2026/../q3.txt"))         # reports/q3.txt

Частая ошибка: путь от текущего каталога, а не от скрипта

Относительный путь в open("config.json") считается от текущего рабочего каталога процесса, а не от папки, где лежит скрипт. Пока вы запускаете программу из папки проекта, все работает. Запуск из другого места, из IDE или из планировщика — и файл «пропадает». Воспроизведем:

import os
import tempfile
from pathlib import Path

project = Path(tempfile.mkdtemp())
(project / "config.json").write_text("{}")

os.chdir(project)
print(Path("config.json").exists())       # True
os.chdir(project.parent)
print(Path("config.json").exists())       # False
print((project / "config.json").exists())  # True

Первая проверка дает True, вторая — False, хотя файл никуда не делся: сменился только текущий каталог. Третья снова True, потому что путь построен от известной точки. Исправление в реальном проекте — считать путь от файла скрипта:

from pathlib import Path

BASE_DIR = Path(__file__).resolve().parent
config_path = BASE_DIR / "config.json"
print(config_path.is_absolute())  # True

Защита от path traversal

Если имя файла приходит от пользователя (форма загрузки, параметр URL), строка вида ../../etc/passwd уводит программу за пределы разрешенного каталога. Это уязвимость path traversal (обход каталога).

Неверная проверка — сравнение строк:

base = "/srv/app/uploads"
user_path = "/srv/app/uploads_evil/shell.php"
print(user_path.startswith(base))  # True

Результат True: соседний каталог uploads_evil начинается с тех же символов и проходит проверку. Кроме того, строка не раскрывает ... Исправление — нормализовать путь и сравнивать по частям пути (is_relative_to доступен с Python 3.9):

from pathlib import Path

BASE = Path("/srv/app/uploads").resolve()

def safe_path(user_name: str) -> Path:
    candidate = (BASE / user_name).resolve()
    if not candidate.is_relative_to(BASE):
        raise ValueError(f"path outside base dir: {user_name!r}")
    return candidate

for name in ["report.pdf", "../../../etc/passwd", "/etc/passwd", "docs/../report.pdf"]:
    try:
        print("ok  ", safe_path(name).relative_to(BASE))
    except ValueError as e:
        print("deny", e)

Ожидаемый вывод:

ok   report.pdf
deny path outside base dir: '../../../etc/passwd'
deny path outside base dir: '/etc/passwd'
ok   report.pdf

Обратите внимание на третий случай: при склейке BASE / "/etc/passwd" абсолютный путь справа отбрасывает левую часть, и без проверки программа читала бы системный файл.

Это базовая проверка, а не полная защита. Надежнее вообще не использовать имя от пользователя как путь: сохранять файл под сгенерированным идентификатором, а исходное имя хранить в базе. Дополнительно нужны права процесса только на свой каталог, запрет символических ссылок из недоверенных источников и учет гонки между проверкой и открытием файла.

Как быстро узнать и скопировать путь

Где Как
Проводник Windows Shift + правый клик по файлу -> «Копировать как путь» (в Windows 11 пункт есть и в обычном меню)
Командная строка Windows Перетащить файл в окно — вставится полный путь; cd без аргументов покажет текущий каталог
Finder в macOS Правый клик с зажатой Option -> «Скопировать путь до…»
Терминал Linux и macOS pwd — текущий каталог, realpath file.txt — абсолютный путь к файлу

Выводы

  • Абсолютный путь начинается от корня или диска и не зависит от места запуска, относительный считается от текущего рабочего каталога.
  • . — текущий каталог, .. — родительский; многоточие специального смысла не имеет.
  • Windows и Unix различаются корнем, разделителем, чувствительностью к регистру и лимитами длины.
  • В коде пути собирают через pathlib, а относительные пути считают от известной точки, например от __file__.
  • Путь от пользователя нормализуют и проверяют через resolve() и is_relative_to(), а не сравнением строк.

Где применяется / связь с практикой

Освойте тему на практике

С путями сталкивается любой, кто пишет скрипты: чтение конфигов, обработка папок с данными, загрузка файлов на сервер, автоматизация рутины. Работу с файловой системой, модулями стандартной библиотеки и первыми проектами на Python системно разбирают на курсе Python Developer. Basic. Попробовать формат и задать вопросы преподавателям можно на бесплатных открытых уроках.

FAQ

Можно ли в Windows писать пути через прямой слеш?
Большинство API Windows и Python принимают C:/Data/file.txt, но часть старых утилит командной строки воспринимает / как начало ключа, поэтому в cmd надежнее \.

Почему в Python пишут r»C:\new»?
Без префикса r комбинация \n превратится в перевод строки. Сырая строка оставляет обратные слеши как есть; альтернатива — pathlib или прямые слеши.

Чем путь к файлу отличается от URL?
URL адресует ресурс в сети по схеме https://, всегда использует / и кодирует спецсимволы; локальный путь описывает место в файловой системе конкретной машины.

OTUS Журнал