История о том, как десктопный клиент при запуске рекурсивно очистил чужую папку и унёс сотни гигабайт проектов, — не курьёз, а закономерность. Почти в каждом проекте есть код, который что-то удаляет рекурсивно: кеш, временные файлы, артефакты сборки, старые бэкапы. Такой код пишется быстро, ревьюится поверхностно и годами работает правильно — до первой пустой переменной или неожиданного симлинка. В этой статье разберём типичные способы выстрелить себе в ногу и покажем, как выстроить защиту в 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 ГБ — не про конкретную программу, а про общую культуру работы с файловой системой. Добавьте проверки сегодня, чтобы завтра не стать героем нового поста.