Удалить все файлы, кроме самого большого

Удалить все файлы, кроме самого большого

У меня есть папка со множеством подпапок. Я хочу удалить все файлы меньшего размера из каждой подпапки, оставив только самый большой файл.

Например:

Subfolder1
---------- File 1 ---- 300k
---------- File 2 ---- 299k
---------- File 3 ---- 800k

Только file 3должно остаться с 800k. Если в папке только один файл, он остается.

Этот код работает, но я не могу поместить его в цикл for (для рекурсии каталога):

find . -type f -maxdepth 1 | sort -n -r | tail -n +2 | xargs -I{} rm -v {}

Как я могу это сделать?

решение1

~$ tree -fQFi --sort=size pluto
"pluto"
"pluto/pluto1"/
"pluto/pluto1/pluto3"/
"pluto/pluto1/pluto3/nozero.txt"
"pluto/pluto1/pluto3/zero ed.txt"
"pluto/pluto1/nozero.txt"
"pluto/pluto2"/
"pluto/pluto2/nozero.txt"
"pluto/pluto2/nozer.txt"
"pluto/pluto2/zero.txt"
"pluto/pluto4"/
"pluto/pluto4/zeroed.txt"
"pluto/zeroed.txt"

4 directories, 8 files

~$ tree -fQFic --noreport --sort=size pluto | \
> awk -F"/" 'NR==1||/\/$/{next}; \
>     {path=""; for(i=1;i<NF;i++) path=path$i; if(a[path]++) print}'
"pluto/pluto1/pluto3/zero ed.txt"
"pluto/pluto2/nozer.txt"
"pluto/pluto2/zero.txt"

~$ tree -fQFic --noreport --sort=size pluto | \
> awk -F"/" 'NR==1||/\/$/{next}; \
>     {path=""; for(i=1;i<NF;i++) path=path$i; if(a[path]++) print}' | \
> xargs rm -v
'pluto/pluto1/pluto3/zero ed.txt' rimosso
'pluto/pluto2/nozer.txt' rimosso
'pluto/pluto2/zero.txt' rimosso

~$ tree -fQFi --sort=size pluto
"pluto"
"pluto/pluto1"/
"pluto/pluto1/pluto3"/
"pluto/pluto1/pluto3/nozero.txt"
"pluto/pluto1/nozero.txt"
"pluto/pluto2"/
"pluto/pluto2/nozero.txt"
"pluto/pluto4"/
"pluto/pluto4/zeroed.txt"
"pluto/zeroed.txt"

4 directories, 5 files

treeсписки по каталогу, а затем по убыванию размера.

  • awk1-я строка кода пропускает tree1-ю строку выводаилистроки с завершающими слешами (т.е. каталоги)
  • awkВторая строка кода создает dirname из полного пути ( forцикл), а затем выводит полные имена путей, если dirname встречалось хотя бы один раз в предыдущих строках (т.е. выводит для каждого каталога, начиная со второго указанного файла)

решение2

Оправдание

Это моя попытка создать команду, которая будет работать слюбойИмя каталога и файла(ов). В общем случае пути в Linux (и имена в файловых системах) могут содержать любые символы, кроме null ( 0x00) и /. Проблемными символами могут быть " " (пробел), любой другой пробел, ', ", новая строка, другие непечатаемые символы. Поэтому важно:

  • отказаться от инструментов, которые заменяют одни символы другими (например, многие реализации lswill print ?вместо непечатаемых символов);
  • передавать все имена как строки с нулевым символом в конце (выбирать инструменты, которые могут их анализировать);
  • цитируйте правильно.

Меня вдохновила дискуссия подэтот другой ответ.


Действующие команды

Тестовая версия, будут lsудалены только следующие файлы:

find -type d -exec sh -c 'find "$0" -maxdepth 1 -mindepth 1 -type f -exec stat --printf "%s %n\0" \{\} + | sort -znr | tail -zn +2' {} \; | cut -zf 2- -d " " | xargs -0r ls -l

Да, я использую lsздесь, несмотря на то, что я только что сказал. Это потому, что lsвывод не анализируется дальше. Я использую его только для отображения результата. Если у вас есть каталоги или файлы с проблемными символами в именах, то вы увидите поведение, lsкоторое должно убедить васникогда не анализироватьls(если только вы не уверены, что с ним вы в полной безопасности). Все равно проблемные имена пройдут весь путь, lsи в этом суть.

Понять тестовую версию(см. ниже некоторые пояснения)и попробуйте, прежде чем вы позволите рабочей версии(ниже)удалите ваши файлы.Помните, я просто случайный парень в Интернете.

Рабочая версия, она удалит ваши файлы:

find -type d -exec sh -c 'find "$0" -maxdepth 1 -mindepth 1 -type f -exec stat --printf "%s %n\0" \{\} + | sort -znr | tail -zn +2' {} \; | cut -zf 2- -d " " | xargs -0r rm

Объяснение

Вот тестовая версия, разделенная на несколько строк (хотя это все еще одна строка bash; обратите внимание, я используюэтот трюкдля встроенных комментариев):

find -type d -exec   `# Find all directories under (and including) the current one.` \
  sh -c '            `# In every directory separately...` \
    find "$0" -maxdepth 1 -mindepth 1 -type f -exec   `# ...find all files,...` \
      stat --printf "%s %n\0" \{\} + |   # ...get their sizes and names,...
    sort -znr |                          # ...sort by size...
    tail -zn +2'                        `# ...and discard the "biggest" entry.` \
    {} \
  \; |                                   # (All the directories have been processed).
cut -zf 2- -d " "  |                     # Then extract filenames...
xargs -0r ls -l                          # ...and ls them (rm in the working version).

Используемые приемы, преодолеваемые препятствия:

  • Инструментам, анализирующим строки, предписано работать со строками, завершающимися нулевым символом:
    • stat --printf "…\0";
    • sort -z, tail -z, cut -z;
    • xargs -0 …;
    • find -print0(в данном примере это не требуется, но в целом встречается очень часто, поэтому я все равно об этом упоминаю).
  • sh -c '…'это способ использования труб внутри find -exec.
  • find -type d -exec sh -c 'find "{}" …прервется для имени каталога, содержащего "; find -type d -exec sh -c 'find "$0" … ' {} \;работает нормально.
  • {}во внутреннем findоператоре экранируются ( \{\}), чтобы предотвратить их замену внешним findоператором.
  • cutможет немедленно следовать tail, он будет работать по одному cutна каталог. Размещение его вне внешнего findделает одиночный cutделает всю резку сразу.
  • Параметр -rпредотвращает xargsзапуск ls( rmв рабочей версии) при отсутствии входных данных xargs.

Связанный контент