Удалить все файлы, кроме как в определенном подкаталоге с помощью find

11

Я хочу рекурсивно удалить все файлы, к которым не было доступа в данный момент, в папке a, кроме всех файлов в подпапке b.

find a \( -name b -prune \) -o -type f -delete

Тем не менее, я получаю сообщение об ошибке:

find: действие -delete автоматически включает -depth, но -prune ничего не делает, когда действует -depth. Если вы хотите продолжить, просто явно используйте опцию -depth.

Добавление -depthприводит bк включению всех файлов , что не должно происходить.

Кто-нибудь знает безопасный способ сделать эту работу?

forthrin
источник
@ MichaelKjörling: я посмотрел на extglob, но как включить все, aкроме a/b?
13
Не cd a && ls -d !(b/*)сработает? (Чтобы сделать это, просто rm -rвместо ls -d.)
CVN
Ваше предложение удаляет вложенные папки. Я хочу сохранить папки нетронутыми. Я хочу найти и удалить все файлы в дереве a(кроме файлов в a/b).
13
Так что просто пропустите -rдо рм. Кажется, что на вопрос, о котором вы спрашиваете, довольно легко ответить, используя расширенное глобирование в bash, и тогда то, что вы будете делать с результатом глобализации, зависит от вас.
CVn
@ MichaelKjörling Только потому, что две проблемы имеют смутно похожие решения, не делает вопросы дубликатами. Большинство решений каждой из двух проблем не решают другую проблему.
Жиль "ТАК - перестань быть злым"

Ответы:

13

TL; DR: лучший способ - использовать -exec rmвместо -delete.

find a \( -name b -prune \) -o -type f -exec rm {} +

Объяснение:

Почему находите жалобу, когда вы пытаетесь использовать -deleteс -prune?

Краткий ответ: потому что -deleteподразумевает -depthи -depthделает -pruneнеэффективным.

Прежде чем мы перейдем к длинному ответу, сначала рассмотрим поведение поиска с и без -depth:

$ find foo/
foo/
foo/f1
foo/bar
foo/bar/b2
foo/bar/b1
foo/f2

Там нет гарантии о заказе в одном каталоге. Но есть гарантия, что каталог обрабатывается до его содержимого. Примечание foo/перед любым foo/*и foo/barперед любым foo/bar/*.

Это может быть отменено с -depth.

$ find foo/ -depth
foo/f2
foo/bar/b2
foo/bar/b1
foo/bar
foo/f1
foo/

Обратите внимание, что теперь все foo/*появляются раньше foo/. То же самое с foo/bar.

Более длинный ответ:

  • -pruneпредотвращает поиск в каталоге. Другими словами -pruneпропускает содержимое каталога. В вашем случае не -name b -pruneпозволяет найти спуск в любой каталог с именем b.
  • -depthделает find для обработки содержимого каталога до самого каталога. Это означает, что к тому времени, когда find начнет обрабатывать запись каталога, bее содержимое уже обработано. Таким образом -prune, неэффективно с -depthпо сути.
  • -deleteподразумевает, -depthчто он может сначала удалить файлы, а затем пустой каталог. -deleteотказывается удалять непустые каталоги. Я думаю, что можно было бы добавить опцию, чтобы заставить -deleteудалить непустые каталоги и / или запретить -deleteподразумевать -depth. Но это уже другая история.

Есть еще один способ добиться того, чего вы хотите:

find a -not -path "*/b*" -type f -delete

Это может или не может быть легче запомнить.

Эта команда все еще спускается в каталог bи обрабатывает каждый отдельный файл в нем только для того, -notчтобы отклонить их. Это может быть проблемой производительности, если каталог bогромен.

-pathработает иначе чем -name. -nameсовпадает только с именем (файла или каталога), а -pathс полным путем. Например соблюдайте путь /home/lesmana/foo/bar. -name -barбудет соответствовать, потому что имя bar. -path "*/foo*"будет соответствовать, потому что строка /fooнаходится в пути. -pathимеет некоторые тонкости, которые вы должны понять, прежде чем использовать его. Прочитайте справочную страницу findдля более подробной информации.

Помните, что это не на 100% надежно. Есть шансы на «ложные срабатывания». То, как команда написана выше, пропускает любой файл, у которого есть родительский каталог, имя которого начинается с b(положительного). Но он также пропустит любой файл, имя которого начинается bнезависимо от позиции в дереве (ложное срабатывание). Это можно исправить написав лучшее выражение, чем "*/b*". Это оставлено в качестве упражнения для читателя.

Я предполагаю, что вы использовали aи в bкачестве заполнителей и настоящие имена больше похожи allosaurusи brachiosaurus. Если вы поставите brachiosaurusвместо bэтого количество ложных срабатываний, будет значительно сокращено.

По крайней мере, ложные срабатывания будут не удалены, так что это будет не так трагично. Кроме того, вы можете проверить наличие ложных срабатываний, выполнив сначала команду без -delete(но не забудьте указать подразумеваемую -depth) и проверить вывод.

find a -not -path "*/b*" -type f -depth
lesmana
источник
-not -pathбыла только вещь! Спасибо за щедрое объяснение!
13
1
Некоторое уточнение того, почему -not -pathработает, а -pruneне будет полезно. Почему может -not -pathсосуществовать с -depth?
Фахим Митха
3

Просто используйте rmвместо -delete:

find a -name b -prune -o -type f -exec rm -f {} +
Стефан Шазелас
источник
1
Можете ли вы объяснить, почему rmработает, а deleteчто нет?
Фахим Митха
1
О, я думаю, может быть потому, что "-delete отказывается удалять непустые каталоги", цитируя @lesmana. Поэтому отказывается удалять непустые каталоги. Но rmне имеет этой проблемы. Но, независимо от этого, разработка была бы хорошей вещью.
Фахим Митха
@FaheemMitha, ответ на этот вопрос в вопросе. -deleteподразумевает -depth, что, очевидно, не может работать с -prune. -pathработает, но не останавливается findот спуска в каталогах, которые не нужно исследовать.
Стефан Шазелас
0

Приведенные выше ответы и объяснения были очень полезны.

Я использую обходные пути "-exec rm {} +" или "-not -path ... -delete", но они могут быть намного медленнее, чем "find ... -delete". Я видел "find ..." -delete "работать в 5 раз быстрее, чем" -exec rm {} + "в глубоких каталогах в файловой системе NFS.

Решение «-не путь» имеет очевидные накладные расходы при просмотре всех файлов в исключенных каталогах и ниже.

"Find .. -exec rm {} +" вызывает rm, который выполняет системные вызовы:

fstatat(AT_FDCWD, path...); 
unlinkat(AT_FDCWD, path, 0)

«Find -delete» выполняет системные вызовы:

 fd=open(dir,...);
 fchdir(fd); 
 fstatat(AT_FDCWD, filename,...)
 unlinkat(dirfd, filename,...)

Таким образом, команда "-exec rm {} +" rm дважды выполняет полный путь к поиску inode для каждого файла, а команда "find -delete" выполняет статистику и отмену ссылки на имя файла в текущем каталоге. Это большая победа, когда вы удаляете много файлов в одном каталоге.

(режим нытья включен (извините))

Похоже, что дизайн взаимодействия между -depth, -delete и -prune без необходимости устраняет наиболее эффективный способ выполнения общего действия «удалять файлы, кроме тех, что находятся в каталогах -prune»

Комбинация "-type f -delete" должна запускаться без -depth, поскольку она не пытается удалить каталоги. В качестве альтернативы, если бы «find» имела действие «-deletefile», которое говорит, что не удаляет каталоги, -depth не должно подразумеваться.

Вызовы xargs или find -exec для команды rm могут быть ускорены, если у rm есть возможность сортировать имена файлов, открывать каталоги и выполнять unlinkat (dir_fd, filename) вместо отмены связывания полных путей. Он уже выполняет unlinkat (dir_fd, имя файла) при рекурсии через каталоги с опцией -r.

(режим нытья выключен)

Дональд Мирс
источник