
У меня есть папка со множеством подпапок. Я хочу удалить все файлы меньшего размера из каждой подпапки, оставив только самый большой файл.
Например:
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
списки по каталогу, а затем по убыванию размера.
awk
1-я строка кода пропускаетtree
1-ю строку выводаилистроки с завершающими слешами (т.е. каталоги)awk
Вторая строка кода создает dirname из полного пути (for
цикл), а затем выводит полные имена путей, если dirname встречалось хотя бы один раз в предыдущих строках (т.е. выводит для каждого каталога, начиная со второго указанного файла)
решение2
Оправдание
Это моя попытка создать команду, которая будет работать слюбойИмя каталога и файла(ов). В общем случае пути в Linux (и имена в файловых системах) могут содержать любые символы, кроме null ( 0x00
) и /
. Проблемными символами могут быть " " (пробел), любой другой пробел,
'
, "
, новая строка, другие непечатаемые символы. Поэтому важно:
- отказаться от инструментов, которые заменяют одни символы другими (например, многие реализации
ls
will 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
.