Ваш пример:
chown :group file
не работает (может работать на некоторых платформах, но не является стандартным ). Обычно при этом меняется владелец файла.
Также обратите внимание, что метод двоеточия в chown был добавлен ок. 25 лет назад, а chgrp
намного старше.
Это очень интересный вопрос, лучшее, что я могу построить, это этот скрипт:
set -e
# set -x
folder=$1
counter=$(tempfile)
# List file extensions
list_extensions() {
find "$folder" -type f |
while read filename
do
basename=${filename##*/}
ext=${basename##*.}
echo ${ext,,} # downcase extensions to prevent duplicates
done |
sort -u
}
list_extensions |
while read extension
do
size=$(find "$folder" -type f -iname "*.$extension" -fprintf $counter. -print0 |
du -hc --files0-from=- | tail -n 1 | sed -E 's/\s+total//')
count=$( wc -c < $counter )
printf "*.%-10s\t%6s files\t%10s\n" "$extension" "$count" "$size"
done
rm $counter
Он не поддерживает сложные имена файлов, может быть много исключений и производительность невысокая, но он работает.
Пример вывода:
*.wma 122 files 411M
*.wpl 16 files 64K
*.xls 2 files 24K
*.xlsx 1 files 28K
*.zip 5 files 333M
duext() {
case "$1" in
-* )
set "./$1"
esac
POSIXLY_CORRECT= find "${1-.}" -type f -exec du {} + | awk '
{
sz=$1
$1=""
sub("^ *","")
sub("^.*/","")
sub("^\\.","")
w=split($0,a,".")
e=tolower(w==1?"*":"*."a[w])
s[e]+=sz
n[e]+=1
}
END {
for (e in s) print 512*s[e]"\t"n[e]"\t"e
}'
}
Использование:duext path
. Значение по умолчанию path
равно .
. Функция должна работать в sh
и совместимых оболочках.
Функция генерирует строки в следующем виде:
s<tab>n<tab>e
где s
— размер используемого диска (в байтах ), n
— количество файлов, e
— расширение. Это отличается от запрошенного вами вывода, потому что я решил оптимизировать для синтаксического анализа. То, что вы называете «расширением», является лишь частью имени файла в *nix. Имена файлов могут содержать пробелы или табуляции. Размещение e
(, которое может содержать пробелы или табуляции )в конце строки, позволяет надежно распознавать другие поля. Например. вы можете легко сортировать по размеру:
duext /home/various/ | sort -rn -k1,1 # optionally: … | column -t
Примечания:
POSIXLY_CORRECT= du …
— это портативный способ использовать размер диска. Он сообщает в блоках по 512 байт, поэтому 512*s[e]
позже в коде awk
. GNU du
предоставляет несколько интересных опций (, например.--apparent-size
); они могут потребовать настройки кода awk
. sub("^\\.","")
несет ответственность за то, что начальная точка в имени не рассматривается как разделитель расширений. Фактически .nfo
интерпретируется как (скрытый )файл без расширения, а не как файл с расширением nfo
. Если это не то, что вы хотите, удалите строку. foo.
)без расширения(foo
). Первый сообщается как *.
; последний сообщается как *
. tolower
, чтобы сделать его чувствительным к регистру -. du
может пропустить или не пропустить файл, если это жесткая ссылка на какой-то уже зарегистрированный файл. Кроме того, find … -exec du {} +
запускает du
столько раз, сколько необходимо (, чтобы избежать argument list too long
), а файлы с жесткими ссылками могут передаваться или не передаваться одному и тому же du
.Вы можете принудительно подсчитывать каждую жесткую ссылку, используяdu -l
(не -портативную опцию в GNUdu
)или переносимую, запустив одну du
для каждого файла:find … -exec du {} \;
. Чтобы надежно подсчитать жесткие ссылки только один раз, вам нужен другой подход (один экземпляр GNU du
и --files0-from=
? ). В общем, можно иметь жесткие ссылки с разными расширениями. Это не проблема, если вы хотите посчитать каждую жесткую ссылку отдельно, но если вы хотите посчитать их как один файл, то неясно, какое расширение назначать. Я не уверен, что под MB
вы подразумеваете мегабайты или мегабайты , я предполагаю последнее. Следующий код должен быть переведен в нужный формат:
yourformat() { awk '
function human(x) {
if (x<1000) {return x} else {x/=1000}
s="kMGTEPZY";
while (x>=1000 && length(s)>1)
{x/=1000; s=substr(s,2)}
return int(10*x+0.5)/10 substr(s,1,1)
}
{
s=$1; n=$2
$1=""; $2=""
sub("^ ","")
print $0" "n" file"(n==1?"":"s")", "human(s)"B"
}'
}
(Примечание:human(x)
было взято из этого ответа и скорректировано.)
Используйте так:
duext /home/various/ | yourformat
duext
использует awk
внутри, и теперь мы передаем его в yourformat
, который также использует awk
. В целом, вместо этого мы могли бы использовать один awk
в одной функции. Еще отдельные awk
позволяют нам поставить, например. sort …
между (в одной функции оболочки или в канале между функциями ). Хотя какая-то сортировка может быть реализована вawk
(или, по крайней мере, в GNU awk
), нет смысла изобретать велосипед. IMO сохранение вывода из первого awk
легко анализируемым - это правильно. Таким образом, вы сможете позже применить любой фильтр и форматирование.
Давайте улучшим ваш формат, чтобы можно было использовать column -t
. А как насчет коэффициента 1024?
myformat() { awk '
function human(x) {
if (x<1000) {return x" "} else {x/=1024}
s="kMGTEPZY";
while (x>=1000 && length(s)>1)
{x/=1024; s=substr(s,2)}
return int(10*x+0.5)/10" "substr(s,1,1)"i"
}
{
s=$1; n=$2
$1=""; $2=""
sub("^ ","")
print $0"\t"n" file"(n==1?"":"s")"\t"human(s)"B"
}'
}
А потом:
duext /home/various/ | sort -nr -k1,1 | myformat | column -t -s "$(printf '\t')"
Примечания:
"$(printf '\t')"
— это портативный способ получить символ табуляции. В некоторых оболочках (, например. в Bash)$'\t'
делает то же самое. column
сам по себе не является переносимым. Откровенно говоря, мне это решение достаточно нравится, чтобы оставить его.Я создал скрипт с именем due
для будущего использования :
#!/bin/sh
duext() {
…
}
myformat {
…
}
duext "${1-.}" | sort -nr -k1,1 | myformat | column -t -s "$(printf '\t')"