find. -type d -printf "%d %p\n" |\
sort -nr |\
perl -pe 's/^\d+\s//;' |\
while read dir; do \
(rmdir "$dir" > /dev/null 2>&1); \
done
Вот как это работает:
rmdir
в списке один за другим Чтобы подсчитать использование диска, а не сумму видимого размера, вам нужно использовать %b
¹ вместо %s
и убедиться, что каждый файл подсчитывается только один раз, поэтому что-то вроде:
LC_ALL=C find. -iname '*.py' -type f -printf '%D:%i\0%b\0%h\0' |
gawk -v 'RS=\0' -v OFS='\t' -v max=50 '
{
inum = $0
getline du
getline dir
}
! seen[inum]++ {
gsub(/\\/, "&&", dir)
gsub(/\n/, "\\n", dir)
sum[dir] += du
}
END {
n = 0
PROCINFO["sorted_in"] = "@val_num_desc"
for (dir in sum) {
print sum[dir] * 512, dir
if (++n >= max) break
}
}' | numfmt --to=iec-i --suffix=B --delimiter=$'\t'
Новые строки в именах каталогов отображаются как \n
, а обратные косые черты (по крайней мере декодируются как таковые в текущей локали² )как \\
.
Если файл найден более чем в одном каталоге, он засчитывается относительно первого найденного в (порядке, не являющемся детерминированным ).
Предполагается, что в среде нет POSIXLY_CORRECT
переменной (, если она есть, установка PROCINFO["sorted_in"]
не влияет на gawk
, поэтому список не будет сортироваться ). Если вы не можете этого гарантировать³, вы всегда можете запустить gawk
как env -u POSIXLY_CORRECT gawk...
(, предполагая GNU env
или совместимый; или(unset -v POSIXLT_CORRECT; gawk...)
).
Несколько других проблем с вашим подходом:
LC_ALL=C
GNU find
не сообщал бы о файлах, имена которых не образуют допустимых символов в локали, так что вы могли пропустить некоторые файлы. {}
в код sh
представляло собой уязвимость, связанную с внедрением произвольного кода. Подумайте, например, о файле с именем $(reboot).py
. Вы никогда не должны этого делать, пути к файлам должны передаваться как дополнительные аргументы и ссылаться в коде с использованием позиционных параметров. echo
нельзя использовать для отображения произвольных данных (, особенно с -e
, что здесь не имеет смысла ). Вместо этого используйте printf
. xargs -r0 du -sch
du
может вызываться несколько раз, если список файлов большой, и в этом случае последняя строка будет включать только итоговую сумму за последний запуск. ¹ %b
сообщает об использовании диска в единицах по 512 -байт. 512 байт — это минимальная степень детализации для выделения диска, поскольку это размер традиционного сектора. Также есть %k
, то есть int(%b / 2)
,но это дало бы неправильные результаты в файловых системах с блоками по 512 байт (блоки файловой системы обычно имеют степень 2 и имеют размер не менее 512 байт)
² Использование LC_ALL=C
для gawk также сделало бы его немного более эффективным, но, возможно, исказило бы вывод в локалях с использованием кодировок BIG5 или GB18030 (, а имена файлов также закодированы в этом наборе символов )как кодировка обратной косой черты также встречается в кодировке некоторых других символов.
³ Учтите, что если ваш sh
равен bash
, POSIXLY_CORRECT
установлен в y
в сценариях sh
и экспортируется в среду, если sh
запускается с -a
или -o allexport
, так что эта переменная тоже может появиться непреднамеренно.
Это может быть намного быстрее, но это не полностью эквивалентно вашему подходу. Файлы подкаталога не учитываются дважды :
.find. -type f -iname '*.py' -printf '%s %h\0' |
awk 'BEGIN { RS="\0"; }; '\
'{ pos=index($0," "); size=substr($0,1,(pos-1)); dir=substr($0,pos+1); gsub("\n","\\n",dir); '\
'if(dir!=lastdir) { if(NR>1) { "numfmt --to=iec-i --suffix=B " sizesum " | tr -d \"\n\"" | getline fsize; print fsize " " lastdir; } '\
'sizesum=size; lastdir=dir; } '\
'else sizesum=sizesum+size; }; '\
'END { "numfmt --to=iec-i --suffix=B " sizesum " | tr -d \"\n\"" | getline fsize; print fsize " " lastdir; }'
3,2KiB./dir1
1,1MiB./dir2
Вдобавок к тому, что он быстрее, он заменяет новые строки литералами \n
. Если вы ожидаете имена каталогов с новыми строками, вам придется обрабатывать их до конца конвейера, чего не делает ваш код.
Я подозреваю, что вам нужно написать свой собственный du.
В настоящее время вы выполняете тройную рекурсию по иерархии, используя два find и du.
Я бы посоветовал начать с пакета Perl File::Find
.
В качестве альтернативы, ваша первая находка может быть выведена с чем-то вроде -printf '%k %h\n'
, а затем вы можете отсортировать по каталогу, использовать perl или awk (или даже bash ), чтобы суммировать каталоги и преобразовать в «человеческий» читаемый, и наконец, сортировка и голова.
В любом случае вам следует A )пройтись по дереву каталогов только один раз, а B )создать как можно меньше процессов.
#!/bin/bash
find. -type f -iname '*.py' -printf '%k %h\n' | sort -k2 | (
at=
bt=
output() {
if [[ -n "$at" ]]
then
printf '%s\t%s\n' "$at" "$bt"
fi
}
while read a b
do
if [[ "$b" != "$bt" ]]
then
output
bt="$b"
at=0
fi
at=$(( $at + $a ))
done
output
) | sort -hr | head -50 | numfmt -d' ' --field=1 --from-unit=Ki --to=iec-i
Примечание. :%k важен. %s сообщает видимый размер,в то время как %k (иdu
)сообщают о размере диска. Они отличаются для разреженных файлов и больших файлов. (Хочешь du --apparent-size
, пусть будет так.)
Примечание. :numfmt должен идти в конце, поэтому он запускается один раз. Используя '%k', необходимо указать единицу измерения из -.
Примечание. :Параметр -d numfmt должен содержать одну вкладку. Я не могу ввести это здесь, и numfmt не примет -d'\t'
. Если разделитель не является табуляцией, интервалы искажаются. Таким образом, я использовал printf вместо эха в основном теле. (В качестве альтернативы можно было бы использовать echo и final sed для замены первого пробела на табуляцию.
Примечание. :Сначала я пропустил первую сортировку и получил повторяющиеся записи для некоторых каталогов при повторном -тестировании.
Примечание. :numfmt появился относительно недавно.