2025, Oct 20 06:17

Как правильно экранировать UNC‑пути в Windows и префикс \\?\\ в Python

Как избежать ошибок с префиксом \\?\\ и двойным экранированием в Python. Корректная обработка UNC‑путей Windows и сериализация путей для конфигов без ловушек.

Когда нужно привести пути Windows к расширенному формату UNC и сериализовать их как строковые литералы для конфигурационных файлов, легко допустить две ошибки: неверно определить уже существующий префикс UNC и допустить двойное экранирование обратных слешей. Обе проблемы быстро проявляются, если на входе уже есть префикс \?\ или используются «сырые» строки.

Демонстрация проблемы

Ниже — минимальная функция, которая пытается добавить префикс UNC и затем экранировать обратные слеши для буквального представления. Она также пытается обнаружить уже существующий префикс. Задумка разумная, а вот результат — нет.

def to_unc_literal(pth: str) -> str:
    if pth.startswith(r"\\?\\"):
        base = pth
    else:
        base = r"\\?\\" + pth
    escaped_out = base.replace("\\", "\\\\")
    return escaped_out

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

src_path = r"\\?\C:\Windows\system32\config\systemprofile\AppData\Local\temp\p\package_abc123\p"
expected_literal = r"\\\\?\\C:\\Windows\\system32\\config\\systemprofile\\AppData\\Local\\temp\\p\\package_abc123\\p"

actual_literal = to_unc_literal(src_path)
print("Input:", repr(src_path))
print("Expected:", repr(expected_literal))
print("Actual:", repr(actual_literal))
print("Match:", actual_literal == expected_literal)

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

Что именно идёт не так

Здесь срабатывают две разные ловушки. Во‑первых, «сырая» строка не может заканчиваться одиночным обратным слешем, поэтому выразить префикс UNC как r"\\?\" невозможно. В обход этого префикс записали как r"\\?\\" с двумя завершающими слешами. Это меняет буквальное содержимое и ломает определение префикса. Во‑вторых, функция безусловно экранирует обратные слеши после модификации пути. Если на входе уже есть префикс \?\, слепая замена приводит к повторному экранированию и раздувает строковый литерал — именно это и видно в ошибочном результате.

Иными словами, код распознаёт неправильный префикс из‑за обходного решения с «сырой» строкой, а затем выполняет двойное экранирование, потому что не различает случаи «уже экранированный литерал», «сырой UNC» и «обычный путь».

Как безопасно исправить логику

Надёжный способ — явно обработать три ситуации. Если значение уже является экранированным литералом UNC (начинается на \\?\ в буквальном виде), вернуть его как есть. Если это «сырой» путь UNC (начинается на \?\), экранировать один раз. В противном случае добавить префикс UNC и тоже экранировать ровно один раз. Кроме того, объявляйте префикс обычной строкой, чтобы конечный обратный слеш был представлен корректно.

def ensure_unc_literal(txt: str) -> str:
    # Уже экранированный литерал UNC (\\?\... в буквальном виде)
    if txt.startswith("\\\\\\\\\?\\\\"):
        return txt
    # Сырой путь UNC (\?\... как фактическое содержимое)
    elif txt.startswith("\\\\?\\"):
        return txt.replace("\\", "\\\\")
    # Обычный путь: добавить префикс UNC и экранировать один раз
    else:
        prefix = "\\\\?\\"
        combined = prefix + txt
        return combined.replace("\\", "\\\\")

Так экранирование становится идемпотентным, а префикс UNC — корректным. Строки для распознавания записаны так, чтобы соответствовать реальному содержимому строк Python во время выполнения, а не их отображаемому виду.

Почему это важно

Ошибки здесь быстро нарастают. Некорректный префикс UNC ломает обработку пути с самого начала. Двойное экранирование даёт нечитаемые значения, не совпадающие с ожидаемыми литералами, что может отразиться на разборе конфигурации, доступе к файлам и отладке. Разделение трёх состояний — уже экранированный литерал, сырой UNC и обычный путь — предотвращает дублирование и тонкие баги.

Итоги

Используйте обычную строку для префикса UNC, чтобы конечный обратный слеш был представлен корректно. Определяйте, является ли путь уже экранированным литералом, «сырым» UNC или обычным путём, и применяйте экранирование ровно один раз. Этого достаточно, чтобы избежать и некорректного префикса, и ловушки двойного экранирования, стабильно получая нужный строковый литерал для конфигурационных файлов.

Статья основана на вопросе на StackOverflow от tandem и ответе от tandem.