Как удалить папку в Linux: rmdir, rm -r, find. Пробный запуск, старые файлы, Permission denied, busy, забитый диск. Проверено на Ubuntu 24.04.
Чтобы удалить папку в Linux, хватает двух команд: rmdir для пустого каталога и rm -r для каталога с файлами. Обе необратимы: на сервере нет корзины, а опечатка в пути или пустая переменная сотрёт гораздо больше, чем вы собирались. Ниже короткий ответ и все варианты, которые чаще всего ищут: удаление по маске и по возрасту, как оставить несколько файлов, что делать с ошибками «Directory not empty», «Permission denied» и «Device or resource busy», как освободить забитый диск и какие привычки не дают rm -rf испортить вам день.
Коротко. Пустой каталог:
rmdir имя. Каталог с содержимым:rm -r имя. Флаг-fтолько глушит ошибки «нет такого файла» и вопросы про защищённые от записи файлы, он не заменяет проверку. Перед удалением посмотрите:ls -la имя,du -sh имя,find имя | head. Удаление по возрасту:find КАТАЛОГ -type f -mtime +30 -delete, а перед ним тот же запуск сrm -rf $VAR/с переменной, которая может оказаться пустой, и не добавляйтеsudo, пока обычная команда не упала по причине, которую вы поняли.
Всё ниже запускалось на тестовом VPS с Ubuntu 24.04.5, GNU coreutils 9.4 (rm, rmdir) и findutils 4.9.0 (find), файловая система ext4, в одноразовом каталоге /tmp/rmlab, который мы в конце удалили. Выводы скопированы из этого прогона. В других системах (BSD, macOS, BusyBox, другие файловые системы) детали отличаются, там мы не проверяли. Опасные примеры с пустой переменной мы только выводили через echo, rm с ними не запускался.
rmdir удаляет каталог, только если он пуст. В этом весь смысл: по ошибке данные не уничтожить, поэтому это самый безопасный инструмент, когда внутри ничего быть не должно.
mkdir empty-dirrmdir empty-dirНет вывода — значит, получилось. Повторите команду, и вы увидите, как выглядит неудача:
rmdir: failed to remove 'empty-dir': No such file or directoryЧтобы удалить каталог вместе с пустыми родителями, добавьте -p. Мы создали a/b/c и выполнили rmdir -p a/b/c: исчезли все три уровня, а ls -d a подтвердил, что a тоже нет. rmdir -p останавливается на первом родителе, где ещё что-то лежит.
Если вы чистите список каталогов и хотите, чтобы rmdir молча пропускал непустые, используйте --ignore-fail-on-non-empty. Мы запустили его на каталоге с одним файлом: ошибки не было, файл остался на месте.
rm удаляет файлы. Если дать ему каталог, обычный rm откажется:
rm d1rm: cannot remove 'd1': Is a directoryФлаг -r (он же -R и --recursive) велит rm зайти внутрь, удалить всё найденное и затем сам каталог:
rm -r d1Вывода нет, код возврата 0, каталога нет. Флаг -f (--force) меняет три вещи: не спрашивает про файлы, защищённые от записи, не жалуется на несуществующие пути, поэтому для отсутствующего пути не возвращает ненулевой код. Мы удалили d1 второй раз:
rm -r d1rm: cannot remove 'd1': No such file or directoryrm -rf d1(вывода нет, код возврата 0)Именно эта тишина делает rm -rf опасным в скриптах: опечатка в имени не даёт ошибки, команда просто ничего не делает или молча удаляет не то. Пишите -f, когда действительно нужно «убедиться, что этого нет», а не по привычке.
Ещё один флаг, о котором стоит знать: rm -d (--dir) удаляет пустой каталог, как rmdir, без захода внутрь. На непустом он отвечает rm: cannot remove 'd1': Directory not empty, то есть это более безопасный родственник rm -r.
Чтобы видеть, что именно удаляется, добавьте -v:
rm -rv project/build/tmpremoved 'project/build/tmp/a.o'removed 'project/build/tmp/b.o'removed directory 'project/build/tmp'Результаты наших проверок в одной таблице. «С содержимым» значит: внутри есть файлы и подкаталоги.
Команда | Пустой каталог | Каталог с содержимым | Пути не существует |
|---|---|---|---|
| Удалён | Ошибка: Directory not empty | Ошибка: No such file or directory |
| Ошибка: Is a directory | Ошибка: Is a directory | Ошибка: No such file or directory |
| Удалён | Ошибка: Directory not empty | Ошибка: No such file or directory |
| Удалён | Удалён со всем содержимым | Ошибка: No such file or directory |
| Удалён | Удалён без вопросов | Тишина, код возврата 0 |
| Удалён | Удалён со всем содержимым ( | Ошибка: No such file or directory |
Дешевле всего застраховаться, просто посмотрев. Мы собрали небольшой каталог project с файлом на 20 МБ внутри build. Перед любым rm -r нужны три ответа: что там лежит, сколько это весит и что именно заденет команда.
ls -la project project/builddu -sh project project/build project/srcfind project | sortfind project -type f | wc -lls -la показывает всё, включая скрытое, du -sh выводит суммарный размер в удобных единицах (путь, уже учтённый в предыдущем аргументе, повторно не считается, поэтому вложенные пути видны одной строкой), find project обходит дерево и печатает все пути, а wc -l считает файлы. Конец нашего вывода:
21M projectprojectproject/buildproject/build/big.binproject/build/tmpproject/build/tmp/a.oproject/build/tmp/b.oproject/srcproject/src/main.c4Ещё три привычки:
echo перед командой: echo rm -r project/build напечатает команду после того, как оболочка раскроет переменные и маски. То, что вы увидите, и получит rm.rm -ri КАТАЛОГ спрашивает перед входом в каждый каталог и перед каждым файлом. rm -rI КАТАЛОГ (заглавная I) спрашивает один раз. У нас они напечатали rm: descend into directory 'project/build'? и rm: remove 1 argument recursively? и ждали ответа; мы ответили n, ничего не удалилось.find с -print, прочитайте список и только потом замените -print на -delete или -exec rm. Примеры ниже.Символы * и ? раскрывает оболочка, а не rm, и делает это до запуска rm. Поэтому список можно посмотреть бесплатно через echo. Мы создали logs/2024-01, logs/2024-02, logs/2025-01 и logs/keep:
echo logs/2024-*rm -r logs/2024-*ls logslogs/2024-01 logs/2024-02(rm ничего не печатает)2025-01keepДве ловушки. Во-первых, звёздочка не захватывает имена, начинающиеся с точки. В каталоге с .hidden и visible команда rm -r lab-dot/* удалила только visible, а ls -A всё ещё показывал .hidden. Скрытое удаляется командой rm -r lab-dot/.[!.]*: она подходит под имена с точкой, но не под . и ... Во-вторых, маска, под которую ничего не подошло, передаётся в rm как буквальный текст, поэтому всегда смотрите на вывод echo.
Попросите find перечислить записи на один уровень вглубь (-mindepth 1 -maxdepth 1), у которых имя не keep.txt (! -name keep.txt). В нашем keepdir лежали keep.txt, a.log, b.log, c.tmp и подкаталог sub:
find keepdir -mindepth 1 -maxdepth 1 ! -name keep.txt -printkeepdir/a.logkeepdir/b.logkeepdir/subkeepdir/c.tmpСписок верный, оставляем тот же отбор и меняем действие. -exec rm -r {} + запускает rm -r на найденном, по нескольку путей за раз:
find keepdir -mindepth 1 -maxdepth 1 ! -name keep.txt -exec rm -r {} +ls -A keepdirkeep.txtВ bash есть способ короче: после shopt -s extglob (включайте отдельной строкой до команды, а не в той же строке) шаблон !(keep.txt) означает «всё, кроме». В каталоге с keep.txt, a.log и b.log команда echo !(keep.txt) напечатала a.log b.log, а rm -- !(keep.txt) оставила только keep.txt. Это работает в интерактивном bash или в скрипте, который сначала включил extglob; в обычном sh не работает. Скрытые файлы он, как и любая маска, пропускает.
Классическая чистка кэшей, загрузок и старых логов. -mtime +30 значит «изменён более 30 полных суток назад». Тестовые данные: три файла с датой 45 дней назад и два с датой двухдневной давности.
find cache -type f -printf '%TY-%Tm-%Td %p\n' | sort2026-09-25 cache/c.tmp2026-09-25 cache/old/a.tmp2026-09-25 cache/old/b.tmp2026-11-07 cache/e.tmp2026-11-07 cache/new/d.tmp-printf с %T показывает даты изменения, так что видно, по чему работает фильтр возраста. Теперь пробный запуск, а затем настоящий:
find cache -type f -mtime +30 -printfind cache -type f -mtime +30 -delete -printОбе команды напечатали одни и те же три пути (cache/c.tmp, cache/old/a.tmp, cache/old/b.tmp); вторая ещё и удалила их. Здесь -type f ограничивает отбор обычными файлами, каталоги не затрагиваются. Каталог old теперь пуст, и find cache -type d -empty -print показывает ровно его. Пустые каталоги удаляются так же через -delete, и чистка завершена:
find cache -type d -empty -deletefind cache | sortcachecache/e.tmpcache/newcache/new/d.tmpМинуты вместо суток: -mmin +90. Файлы, которые давно не читали: -atime, но во многих системах диск смонтирован с параметрами, из-за которых время доступа ненадёжно, поэтому лучше -mtime. Как запускать такую чистку по расписанию, читайте в статье systemd-таймеры или cron.
find вычисляет выражение слева направо, а -delete — действие, которое срабатывает для каждого файла, дошедшего до него. Поставьте его перед фильтром, и фильтр опоздает. В каталоге с keep.conf и x.log:
find gotcha -name '*.log' -printfind gotcha -print -name '*.log'gotcha/x.loggotchagotcha/keep.confgotcha/x.logВторая команда напечатала всё, потому что -print стоял раньше -name. Затем мы запустили find gotcha -delete -name '*.log' на этом одноразовом каталоге: исчезли keep.conf, x.log и сам каталог gotcha. Правило: фильтры сначала, -delete в самом конце, и всегда та же команда с -print перед ней. Бывает и обратная ошибка: find ne -type d -name inner -delete на каталоге с файлом внутри заканчивается сообщением find: cannot delete 'ne/inner': Directory not empty, потому что фильтр пропустил файл. Без фильтра find ne -delete удаляет всё дерево, от вложенного к родителям.
Полное сообщение из нашего прогона:
rmdir: failed to remove 'site': Directory not emptyВнутри что-то есть, возможно, скрытое. Проверьте командой ls -A site (заглавная A показывает файлы с точкой, но без . и ..). В нашем site лежали index.html и assets; глубже rmdir site/assets/img упала так же, потому что там был logo.png. Либо опустошите каталог, либо решите, что содержимое можно убрать, и выполните rm -r site. Не хватайтесь за rm -rf только потому, что rmdir пожаловалась: это система сообщила вам, что каталог не пуст.
Удаление файла определяется не правами на сам файл, а правами на каталог, в котором он лежит. Нужны права на запись и на вход (x) в этот каталог. Мы воспроизвели два частых случая от имени непривилегированного пользователя (nobody).
Случай первый: каталог без права записи (chmod 555) с файлом внутри.
rm -r lockedrm: cannot remove 'locked/f': Permission deniedrmdir lockedrmdir: failed to remove 'locked': Directory not emptyКаталог ваш, значит, верните себе право записи на всё дерево и повторите: chmod -R u+w locked && rm -r locked. Сработало.
Случай второй: каталог, созданный root, внутри общего.
rm -r rootsubrm: cannot remove 'rootsub/file': Permission deniedТут владелец другой. Узнайте, кто именно, командой ls -ld rootsub. Если данные действительно ваши (например, файлы, созданные контейнером Docker от имени root), повторите команду с sudo: sudo rm -r rootsub. Сначала сделайте проверки через ls, du и find -print: с sudo пропадает последняя страховка, ведь root обходит проверки прав, которые только что уберегли вас от опечатки. Если не уверены, что значат биты прав, их показывает ls -l, а объясняет man chmod.
Эти две ошибки отличаются от «Permission denied»: они возникают и у root.
Operation not permitted на своём файле или под root обычно означает, что у файла стоит атрибут неизменяемости (то же сообщение дают атрибут только-добавления +a и sticky-бит у каталога). Мы выставили его командой chattr +i:
lsattr imm/f----i---------e------- imm/frm -r immrm: cannot remove 'imm/f': Operation not permittedchattr -i imm/f && rm -r immПосле chattr -i удаление прошло. Атрибут есть в ext4; другие файловые системы могут его не поддерживать.
Device or resource busy появляется, когда каталог — точка монтирования. Мы примонтировали маленькую tmpfs в busy/mnt:
rmdir busy/mntrmdir: failed to remove 'busy/mnt': Device or resource busyumount busy/mnt && rmdir busy/mnt busyПроверьте через findmnt -T КАТАЛОГ или mount | grep КАТАЛОГ, прежде чем удалять каталог, который настраивал кто-то другой. Не запускайте rm -rf на смонтированном каталоге: он удалит содержимое того, что там смонтировано (диск с данными, сетевую шару), а не просто пустую точку монтирования. В нашем тесте содержимое пропало первым, а потом rm написал «Device or resource busy», потому что сама точка монтирования остаётся. Параметр --one-file-system пропускает монтирования внутри дерева, но не спасает, если сам аргумент и есть точка монтирования. Сначала размонтируйте, потом удалите пустой каталог.
Когда в каталоге сотни тысяч файлов, rm * может не запуститься вовсе: оболочка раскрывает звёздочку в командную строку длиннее допустимой системой. Мы создали 200 000 файлов:
rm many/*/usr/bin/rm: Argument list too longfind не собирает гигантскую командную строку, он удаляет по ходу обхода:
time find many -type f -deletereal 0m2.670suser 0m0.248ssys 0m2.113sНа нашем тестовом VPS 200 000 пустых файлов ушли меньше чем за три секунды. Затем rmdir many закончила дело. rm -r many должна сработать, ведь получает один аргумент (мы её не запускали; ограничение касается общего размера аргументов, ARG_MAX на этом сервере 2097152 байта). Если на деле проблема в том, что удаление не помогает, а система пишет «No space left on device» при свободных гигабайтах, причиной могут быть inode: читайте No space left on device: inode.
На сервере большой каталог обычно удаляют из-за заполненного диска. Не гадайте: найдите самый тяжёлый каталог, потом удаляйте. Мы создали hog/a (60 МБ) и hog/b (10 МБ):
du -h --max-depth=1 hog | sort -hfind hog -type f -size +20M -exec ls -lh {} +11M hog/b61M hog/a71M hog-rw-r--r-- 1 root root 60M Nov 9 15:08 hog/a/big1du --max-depth=1 печатает размер каждого вложенного каталога первого уровня, sort -h сортирует размеры в человекочитаемом виде, так что самый большой оказывается последним, а вторая команда выводит отдельные файлы крупнее 20 МБ. На настоящем сервере начните с / командой du -xh --max-depth=1 / (-x не выходит за пределы одной файловой системы) и спускайтесь уровень за уровнем. Не удаляйте системные каталоги вроде /usr или /var/lib «ради места»: ищите логи, кэши, старые бэкапы и загрузки. Для образов Docker, томов и логов контейнеров есть отдельная инструкция: Docker забил диск: prune и логи.
Файл, удалённый, пока процесс держит его открытым, занимает место до тех пор, пока процесс не закроет его или не завершится. Мы создали файл на 100 МБ, запустили на нём tail -f и удалили его:
df -m /tmp/rmlab (до): занято 6428 МБrm held.bindf -m /tmp/rmlab (после): занято 6428 МБls -l /proc/PID/fd | grep heldlr-x------ 1 root root 64 Nov 9 15:08 3 -> /tmp/rmlab/held.bin (deleted)lsof +L1 | grep rmlabtail 68131 104857600 (deleted)kill PID, затем снова df: занято 6328 МБЦифра не менялась, пока мы не завершили процесс. lsof +L1 показывает открытые файлы без ссылок, то есть как раз удалённые, но удерживаемые; пометка (deleted) видна и в /proc/PID/fd. Лечится остановкой или перезапуском программы, которая держит файл (чаще всего это запись лога), либо обнулением большого лога вместо удаления: : > /путь/к/big.log очищает его на месте. Что безопаснее, зависит от программы; для службы под systemd чистый путь — перезапуск.
Пробелы разбивают команду на отдельные аргументы. Без кавычек rm -r My Folder ищет два пути: My и Folder:
rm -r My Folderrm: cannot remove 'My': No such file or directoryrm: cannot remove 'Folder': No such file or directoryrm -r 'My Folder'В тот раз ничего не потерялось, но если бы существовал каталог My, он бы ушёл. Всегда берите пути в кавычки или дополняйте их клавишей Tab. Имя, начинающееся с дефиса, читается как параметр:
rm -r -oldstuffrm: invalid option -- 'o'Try 'rm ./-oldstuff' to remove the file '-oldstuff'.rm -r -- -oldstuffДвойной дефис -- значит «параметры закончились»; сработает и ./-oldstuff.
Символическая ссылка — маленький файл, указывающий на каталог. Чтобы убрать ссылку, примените rm к ней, без -r и без косой черты в конце:
ln -s real linkrm linkls realimportant.txtЦель уцелела. rm -r link без косой черты в нашем тесте тоже удалила только ссылку, а rmdir link отвечает Not a directory. Опасна косая черта в конце, которую добавляет автодополнение по Tab на ссылке. В той же схеме:
rm -r link/rm: cannot remove 'link/': Not a directoryls -ld linklrwxrwxrwx 1 root root 4 Nov 9 15:09 link -> realls real(пусто)Ошибка выглядит безобидно, но ссылка осталась, а целевой каталог real опустел. С косой чертой в конце GNU rm (coreutils 9.4, в других версиях поведение может отличаться) в нашей системе прошёл по ссылке, удалил содержимое цели и только потом споткнулся на самой ссылке. rm -rf link2/ сделала то же с другой целью. Нужно просто убрать ссылку — удаляйте её имя без косой черты. Висячая ссылка (указывает в никуда) тоже удаляется обычным rm.
Большинство катастроф начинается с пустой переменной. Скрипт делает rm -rf "$BUILD_DIR/", а BUILD_DIR так и не задана. Мы воспроизводим только раскрытие, через echo:
DIR=''; echo "would run: rm -rf $DIR/"would run: rm -rf /DIR=''; echo "would run: rm -rf $DIR/*"would run: rm -rf /*Первая форма нацелена на корневой каталог. У современного GNU rm есть предохранитель --preserve-root, который по умолчанию отказывается рекурсивно работать с /; мы его не запускали и сообщения не видели. Вторая форма этим предохранителем сама по себе не закрыта: оболочка раскрывает /* в список всех каталогов верхнего уровня до того, как rm их увидит, и rm получает /bin /boot /etc /home ... отдельными аргументами. Не полагайтесь на защиту, о которой вы не прочитали в руководстве своей версии.
Защита стоит по одной строке:
set -u в начале скрипта останавливает оболочку на неустановленной переменной. Наш тест: bash: line 1: UNSETVAR: unbound variable, код возврата 127 (во всяком случае, не ноль).${DIR:?сообщение} останавливает выполнение, если DIR не задана или пуста: bash: line 1: DIR: refusing empty DIR. Пишите rm -rf "${DIR:?}/" или лучше rm -rf -- "${DIR:?}" без косой черты.cd /some/dir && rm -rf ./*, а не cd /some/dir; rm -rf *. С ; неудачный cd оставит вас в прежнем каталоге, и команда выполнится там. Форму с && мы проверили на несуществующем каталоге и echo справа: cd упал, правая часть не выполнилась.find -print, о которой шла речь выше.Не рассчитывайте на это. rm убирает имена, корзины для восстановления нет. Мы сделали каталог с заметкой внутри, удалили его, и ls ответил No such file or directory. Специальные программы иногда находят блоки данных на отмонтированном диске, но шанс падает с каждой записью после удаления и зависит от файловой системы и диска, поэтому мы ничего не обещаем. Настоящая защита — резервная копия, которую вы хотя бы раз разворачивали. Если только что удалили что-то важное, перестаньте писать на этот диск (остановите службы, которые пишут на него логи) и выясните, есть ли снимок или бэкап.
Две распространённые подушки.
Интерактивный алиас. Добавьте в ~/.bashrc строку alias rm='rm -i'. Тогда каждый набранный вручную rm сначала спрашивает. Мы проверили, как:
echo n | rm -i ifilerm: remove regular file 'ifile'?Ответ n сохранил файл. Два ограничения: алиас живёт только в вашей интерактивной оболочке, скрипты и sudo rm он не покрывает, а те, кто привык нажимать y, перестают читать вопрос. Считайте это лежачим полицейским, а не замком.
Корзина для командной строки. Пакет trash-cli (sudo apt install trash-cli) даёт trash-put, который переносит объект в каталог корзины вместо удаления. Мы поставили версию 0.23.11.10:
trash-put pre2ls pre2ls: cannot access 'pre2': No such file or directorytrash-list2026-11-09 15:08:59 /tmp/rmlab/pre2Каталог ушёл с прежнего места, но восстанавливается командой trash-restore (она интерактивна и спрашивает, что вернуть), пока вы не выполните trash-empty. У каждого раздела диска своя корзина, поэтому файл с другого раздела хранится там же. Место корзина не освобождает; в этом её цена. На сервере она хороша для ручной работы с важными данными и непригодна для ротации логов или чистки сборок, где нужен find -delete в задании по расписанию.
Пустую — rmdir папка. С файлами — rm -r папка. Сначала выполните ls -la папка, чтобы знать, что уйдёт вместе с ней.
rmdir работает только с пустыми каталогами и иначе отказывает, так что данные уничтожить ею нельзя. rm -r удаляет каталог и всё под ним.
Удаляет рекурсивно (-r) и принудительно (-f): без подтверждения для файлов, защищённых от записи, и без ошибки для несуществующих путей. Она не «мощнее» rm -r, а тише. Права, которых у вас нет, она не обходит.
Командой rm -r каталог. Если она падает с Permission denied, посмотрите, кто владелец каталога и его родителя, прежде чем добавлять sudo.
find папка -mindepth 1 -delete убирает всё внутри, включая скрытые файлы и подкаталоги, а папка остаётся (проверено: остался только пустой каталог). Сначала запустите с -print вместо -delete.
Типичные причины: чаще всего это точка монтирования (Device or resource busy), у файла, как правило, атрибут неизменяемости (Operation not permitted; иногда +a или sticky-бит) или вы удаляли не тот путь, что думали, а -f скрыл ошибку. Запустите снова без -f и прочитайте сообщение.
find /путь -type f -mtime +N -delete, после той же команды с -print. Опустевшие каталоги затем убираются командой find /путь -type d -empty -delete.
rmdir. Каталог с содержимым — rm -r. Флаг -f только заглушает ошибки.ls -la, du -sh, find ... -print, echo rm ....find: фильтры сначала, -delete в конце, а перед ним та же команда с -print.rm -rf $VAR/ без ${VAR:?} или set -u и не ставьте косую черту в конце имени символической ссылки.du, удалите и проверьте df: если цифра не упала, файл держит процесс.