Как удалить все пустые каталоги в поддереве?


151

Как я могу удалить все пустые каталоги в поддереве? Я использовал что-то вроде

find . -type d -exec rmdir {} 2>/dev/null \;

но мне нужно запустить несколько раз, чтобы удалить каталоги, содержащие только пустые каталоги. Более того, он довольно медленный, особенно под Cygwin.


Смотрите также emacs.stackexchange.com/q/12190/2264 для решения emacs.
Шон Оллред

Ответы:


222

Комбинируя findопции и предикаты GNU , эта команда должна выполнить работу:

find . -type d -empty -delete
  • -type d ограничивает каталоги
  • -empty ограничивает пустые
  • -delete удаляет каждый каталог

Дерево идет от листьев без необходимости указывать, -depthкак это подразумевается -delete.


2
-deleteуже подразумевается, -depthпоэтому вам не нужно указывать это вручную.
Джамадагни

1
Спасибо, я этого не осознавал. Ответ обновлен.
Кристоф Древет-Дроге,

11
Я бы добавил -mindepth 1сюда, чтобы предотвратить удаление самого исходного каталога, если он будет пустым.
Грег Дубицки

2
Отлично, но не работает на моих старых хостах SunOS ...
dokaspar

2
!имеет особое значение для оболочки. Вам нужно избежать этого. Что-то вроде: \! -name 'Completed'прямо перед этим -deleteдолжно работать. Или вы просто поместите файл маркера в этот каталог.
Кристоф Древет-Дрогует

53

Перечислите каталоги, глубоко вложенные в первую очередь.

find . -depth -type d -exec rmdir {} \; 2>/dev/null

(Обратите внимание, что перенаправление применяется к findкоманде в целом, а не только к rmdir. Перенаправление только для rmdirможет привести к значительному замедлению, так как вам потребуется вызвать промежуточную оболочку.)

Вы можете избежать работы rmdirс непустыми каталогами, передав -emptyпредикат для поиска. GNU find проверяет каталог, когда он собирается выполнить команду, поэтому каталоги, которые только что были очищены, будут выбраны.

find . -depth -type d -empty -exec rmdir {} \;

Другой способ ускорить работу - сгруппировать rmdirвызовы. Оба, вероятно, будут заметно быстрее, чем оригинал, особенно при Cygwin. Я не ожидаю большой разницы между этими двумя.

find . -depth -type d -print0 | xargs -0 rmdir 2>/dev/null
find . -depth -type d -exec rmdir {} + 2>/dev/null

Какой метод быстрее, зависит от того, сколько у вас непустых каталогов. Вы не можете комбинировать -emptyс методами для группировки вызовов, потому что тогда каталоги, которые содержат только пустые каталоги, не являются пустыми к тому моменту, когда findони на них смотрят.

Другим способом было бы запустить несколько проходов. Быстрее ли это, зависит от многих вещей, в том числе от того, может ли вся иерархия каталогов оставаться в дисковом кэше между findзапусками.

while [ -n "$(find . -depth -type d -empty -print -exec rmdir {} +)" ]; do :; done

Или используйте zsh. Спецификатор glob F соответствует непустым каталогам, поэтому /^Fсоответствует пустым каталогам. Каталоги, которые содержат только пустые каталоги, не могут быть сопоставлены так легко.

while rmdir **/*(/N^F); do :; done

(Это заканчивается, когда rmdirполучает пустую командную строку.)


Вот и все. Вместо 90 секунд это займет 0,90 с.
Maaartinus

@maaartinus: Мне любопытно: у тебя есть подобный набор данных, без которого ты можешь попробовать -p? Я бы не подумал, что это будет иметь значение.
Жиль

3
@maartinus - другие небольшие оптимизации: добавление -emptyдолжно работать с этим (хотя я не уверен точно, сколько оно получит). И очень, очень тривиально, так как вы, вероятно, не хотите удалять ., используйте -mindepth 1.
Mattdm

Это было не удаление, а запуск процесса, который занимал почти все время. Я упустил -depthаргумент, который делает rmdir -pбесполезным. Я уже изменил свой комментарий. 90-е годы были моей первоначальной попыткой; Здесь нет ничего удивительного.
Maaartinus

2
Я понял, что мы можем полностью удалить rmdirкомандный вызов, по крайней мере, с помощью GNU find, с помощью этой команды:find . -depth -type d -empty -delete
Кристоф Древет-Дроге

6

Если вы просто лавировать -pна свой контекстуальный rmdir, который будет работать за один проход. Он не будет красивым или оптимальным, но он должен получить все. Это говорит rmdir об удалении любых непустых родительских каталогов той, которую вы удаляете.

Вы можете немного сэкономить, добавив -emptyтест для поиска, чтобы он не беспокоился о непустых каталогах.


3

find . -depth -type d -exec rmdir {} +

это самый простой и стандартный ответ на этот вопрос.

К сожалению, все приведенные здесь ответы зависят от конкретных улучшений, которые существуют не во всех системах.


3
Этот ответ вызывает ошибку для каждого каталога, который не может быть удален, что может быть меньше, чем желательно.
Виллем ван

0

find . -type d -printf "%d %p\n" |\ sort -nr |\ perl -pe 's/^\d+\s//;' |\ while read dir; do \ (rmdir "$dir" > /dev/null 2>&1); \ done

Вот как это работает:

  1. Рекурсивно перечислите все каталоги вместе с их глубиной
  2. Сортировать по убыванию их глубины
  3. Отфильтровать только пути к каталогам
  4. Запуск rmdirпо списку один за другим

0

Я использую эти псевдонимы для часто используемых findкоманд, особенно когда я очищаю дисковое пространство с помощью dupeguru , где удаление дубликатов может привести к большому количеству пустых каталогов.

Комментарии внутри, .bashrcтак что я не забуду их позже, когда мне нужно настроить его.

# find empty directories
alias find-empty='find . -type d -empty'

# fine empty/zero sized files
alias find-zero='find . -type f -empty'

# delete all empty directories!
alias find-empty-delete='find-empty -delete'

# delete empty directories when `-delete` option is not available.
# output null character (instead of newline) as separator. used together
# with `xargs -0`, will handle filenames with spaces and special chars.
alias find-empty-delete2='find-empty -print0 | xargs -0 rmdir -p'

# alternative version using `-exec` with `+`, similar to xargs.
# {}: path of current file
# +: {} is replaced with as many pathnames as possible for each invocation.
alias find-empty-delete3='find-empty -exec rmdir -p {} +'

# for removing zero sized files, we can't de-dupe them automatically
# since they are technically all the same, so they are typically left
# beind. this removes them if needed.
alias  find-zero-delete='find-zero -delete'
alias find-zero-delete2='find-zero -print0 | xargs -0 rm'
alias find-zero-delete3='find-zero -exec rm {} +'

-2

rm -r */Команда сработала легко для меня. rmНеобходимо -fпринудительно удалить каталоги с файлами. rm -rследует удалять только пустые каталоги. Я открыт, почему это может быть неправильно. Это также должно оставить файлы, так как */смотрит только на папки.


1
Я настоятельно рекомендую сначала тщательно протестировать его, так как rmон предназначен главным образом для удаления файлов. Хотя */только соответствует каталогам, я понятия не имею, что он делает на более глубоких уровнях. Я также могу представить, что он работает только на некоторых системах.
Maaartinus
Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.