История о том, как десктопный клиент при запуске рекурсивно очистил чужую папку и унёс сотни гигабайт проектов, — не курьёз, а закономерность. Почти в каждом проекте есть код, который что-то удаляет рекурсивно: кеш, временные файлы, артефакты сборки, старые бэкапы. Такой код пишется быстро, ревьюится поверхностно и годами работает правильно — до первой пустой переменной или неожиданного симлинка. В этой статье разберём типичные способы выстрелить себе в ногу и покажем, как выстроить защиту в Bash и Python.

Почему рекурсивное удаление опасно

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

Вторая проблема — отсутствие проверок. Разработчики полагаются на то, что «входные данные всегда правильные», и не валидируют путь перед удалением. В результате один сбой в конфигурации или опечатка в переменной приводят к необратимым последствиям. Восстановление данных стоит времени и денег, а иногда невозможно вовсе.

Типичные ошибки в Bash

Самый распространённый антипаттерн — конструкция вида rm -rf $DIR/* без кавычек и проверок. Если переменная DIR пуста или содержит пробелы, команда может удалить не то, что задумано. Ещё хуже — rm -rf /$DIR, где отсутствие слеша или его лишний символ меняют смысл пути.

  • Отсутствие кавычек вокруг переменных. Пустая переменная превращает rm -rf $DIR/ в rm -rf /.
  • Игнорирование симлинков. rm -rf следует по символическим ссылкам, если путь заканчивается на /, что может привести к удалению содержимого целевой директории.
  • Отсутствие проверки существования и типа пути. Скрипт не убеждается, что удаляемая директория действительно та, за которую себя выдаёт.
  • Работа без «предохранителя». Нет проверки на корневые и системные каталоги, нет подтверждения перед удалением.

Чтобы избежать этих ошибок, используйте set -euo pipefail в начале скрипта, всегда заключайте переменные в кавычки и проверяйте путь перед удалением. Например, можно добавить функцию, которая отказывается удалять, если путь пуст, равен / или содержит подозрительные символы.

Безопасное удаление в Python

В Python аналогичные риски связаны с shutil.rmtree и os.remove. Если путь сформирован из ненадёжных данных, можно случайно удалить не ту директорию. Кроме того, shutil.rmtree по умолчанию следует по симлинкам, что может привести к неожиданному удалению.

Рекомендуется перед удалением явно проверять, что путь является директорией, не является символической ссылкой и находится внутри ожидаемой родительской папки. Например, можно использовать pathlib.Path.resolve() и сравнивать с базовым каталогом. Также стоит добавить проверку на то, что путь не является корнем файловой системы или домашней директорией.

  • Проверка типа. Убедитесь, что path.is_dir() и не path.is_symlink().
  • Проверка вложенности. Разрешайте удаление только внутри заданной рабочей директории.
  • Явный отказ от опасных путей. Блокируйте /, C:\, домашние каталоги и системные папки.
  • Логирование. Записывайте, что и когда удаляется, чтобы можно было отследить инцидент.

Пример безопасной функции на Python может включать проверку path.resolve().is_relative_to(base_dir) (доступно с Python 3.9) и отказ, если путь не является подкаталогом базовой директории. Также полезно использовать shutil.rmtree с параметром ignore_errors=False и обрабатывать исключения.

Практические шаги и критерии выбора

Внедрение безопасного удаления не требует много времени, но снижает риск катастрофы. Вот конкретные шаги, которые можно предпринять в своём проекте:

  • Аудит существующего кода. Найдите все места, где используется рекурсивное удаление, и проверьте их на описанные ошибки.
  • Добавьте защитные проверки. В Bash — проверка на пустоту, на корень, на симлинки; в Python — проверка вложенности и типа.
  • Используйте «сухой прогон». Перед удалением выводите список файлов, которые будут удалены, и запрашивайте подтверждение (хотя бы в интерактивном режиме).
  • Настройте логирование и мониторинг. Записывайте операции удаления в лог, чтобы при инциденте можно было восстановить картину.
  • Ограничьте права. Запускайте скрипты с минимально необходимыми правами, чтобы даже при ошибке ущерб был меньше.

Выбор между Bash и Python зависит от контекста. Для простых задач очистки в скриптах развёртывания достаточно Bash с жёсткими проверками. Для более сложной логики, требующей переносимости и тестируемости, лучше подходит Python. В любом случае ключевой принцип — не доверять путям и всегда проверять их перед удалением.

И помните: история с 800 ГБ — не про конкретную программу, а про общую культуру работы с файловой системой. Добавьте проверки сегодня, чтобы завтра не стать героем нового поста.