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

Например:

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 {}

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

2 ответа2

0

обоснование

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

  • отказаться от инструментов, которые заменяют одни символы другими (например, многие реализации 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 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 нет ввода.
0
~$ 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 по каталогу, а затем по убыванию размера.

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

Всё ещё ищете ответ? Посмотрите другие вопросы с метками .