Видаліть усі файли, крім певного підкаталогу з find


11

Я хочу рекурсивно видалити всі файли, не доступні за деякий час у папці a, за винятком усіх файлів у підпапці b.

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

Однак я отримую повідомлення про помилку:

find: Дія -delete автоматично включається -depth, але -prune нічого не робить, коли -depth діє. Якщо ви хочете продовжувати роботу, просто скористайтеся опцією -depth.

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

Хтось знає безпечний спосіб зробити цю роботу?


@ MichaelKjörling: Я дивився на extglob, але як ви включаєте все, aкрім a/b?
1313

Не буде cd a && ls -d !(b/*)працювати? (Для цього, rm -rа не ls -d.)
CVn

Ваша пропозиція видаляє підпапки. Я хочу, щоб папки були недоторканими. Я хочу знайти та видалити всі файли в дереві під a(крім файлів під a/b).
1313

Тому просто пропустіть -rrm. Здається, на те, про що ви питаєте, досить легко відповісти, використовуючи розширений глобус 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змушує обробляти вміст каталогу перед самим каталогом. Це означає, що до моменту, коли знахідка обробляє запис каталогу, 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

-not -pathбула просто річ! Дякую за щедре пояснення!
1313

1
Деякі розробки того, чому -not -pathпрацює, поки -pruneне буде корисним. Чому можна -not -pathспівіснувати -depth?
Faheem Mitha

3

Просто використовуйте rmзамість -delete:

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

1
Чи можете ви детальніше пояснити, чому це rmпрацює, а deleteні?
Faheem Mitha

1
О, я думаю, може тому, що "-delete відмовляється видаляти непорожні каталоги.", Цитувати @lesmana. Тож відмовляється видаляти непорожні каталоги. Але rmце не має проблеми. Але, незалежно, розробка була б хорошою справою.
Faheem Mitha

@FaheemMitha, відповідь на це - у питанні. -deleteМається на увазі -depth, що, очевидно, не може працювати -prune. -pathпрацює, але не зупиняється на findспуску в каталоги, які йому не потрібно досліджувати.
Стефан Шазелас

0

Наведені вище відповіді та пояснення були дуже корисними.

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

Рішення '-not path' має очевидні витрати на перегляд усіх файлів у виключених каталогах та нижче.

"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 каталоги"

Комбінація "-типу f -delete" повинна бути спроможна запускатись без -depth, оскільки вона не намагається видалити каталоги. Крім того, якби "find" мав дію "-deletefile", в якій сказано, що не видаляти каталоги, -depth не потрібно мати на увазі.

Команди xargs або find -exec до команди rm можуть бути прискорені, якщо у rm була можливість сортувати імена файлів, відкривати каталоги та робити unlinkat (dir_fd, ім'я файлу) замість від'єднання повних шляхів. Він вже робить unlinkat (dir_fd, ім'я файлу), коли повторюється через каталоги з параметром -r.

(вимкнено режим)

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.