Как сообщить количество файлов во всех подкаталогах?

24

Мне нужно проверить все подкаталоги и сообщить, сколько файлов (без дальнейшей рекурсии) они содержат:

directoryName1 numberOfFiles
directoryName2 numberOfFiles
Застенчивый мальчик
источник
Почему вы хотите использовать, findкогда Bash будет делать? (shopt -s dotglob; for dir in */; do all=("$dir"/*); echo "$dir: ${#all[@]}"; done): для всех каталогов подсчитайте количество записей в этом каталоге (включая файлы со скрытыми точками, исключая .и ..)
января
@janmoesen Почему ты не ответил? Я новичок в сценариях оболочки, но я не вижу никаких ошибок с вашим методом. Для меня это выглядит как лучший способ. Никто не проголосовал за ваш комментарий, но никто не прокомментировал, почему это может быть плохо. Ответы, на которые проголосовали, имеют гораздо больше повторений, чем вы, так что меня удивляет, что я что-то упустил
Токсалот
@toxalot: я не удосужился добавить его в качестве ответа, потому что он был очень коротким (и, возможно, слегка снисходительным по тону). Не стесняйтесь поднять комментарий. :-) Кроме того, вопрос несколько неясен в отношении того, что означает «сколько файлов». Мое решение считает "обычные" файлы и каталоги; может быть, на самом деле плакат означал «файлы, а не каталоги». Еще одна вещь, которую нужно иметь в виду, заключается в том, что эта глобализация не учитывает «скрытые» файлы точек. Тем не менее, есть способы обойти обе эти проблемы. Но опять же: не уверен в точных требованиях оригинального плаката.
Янмезен

Ответы:

30

Это делает это безопасным и портативным способом. Это не запутает странные имена файлов.

for f in *; do [ -d ./"$f" ] && find ./"$f" -maxdepth 1 -exec echo \; | wc -l && echo $f; done

Обратите внимание, что сначала будет напечатано количество файлов, а затем имя каталога в отдельной строке. Если вы хотите сохранить формат OP, вам понадобится дополнительное форматирование, например

for f in *; do [ -d ./"$f" ] && find ./"$f" -maxdepth 1 -exec echo \;|wc -l|tr '\n' ' ' && echo $f; done|awk '{print $2"\t"$1}'

Если у вас есть определенный набор подкаталогов, которые вас интересуют, вы можете заменить их на *них.

Почему это безопасно? (и поэтому достойный скрипта)

Имена файлов могут содержать любые символы, кроме /. Есть несколько символов, которые обрабатываются специально либо оболочкой, либо командами. К ним относятся пробелы, переводы строки и тире.

Использование for f in *конструкции - это безопасный способ получить каждое имя файла, независимо от того, что оно содержит.

Если у вас есть имя файла в переменной, вы все равно должны избегать таких вещей, как find $f. Если бы $fсодержалось имя файла -test, вы findбы пожаловались на вариант, который вы только что дали. Чтобы избежать этого, используйте ./перед именем; таким образом, он имеет то же значение, но больше не начинается с тире.

Новые строки и пробелы также являются проблемой. Если $fв качестве имени файла содержится "привет, приятель" find ./$f, есть find ./hello, buddy. Вы говорите, findчтобы посмотреть ./hello,и buddy. Если таковых не существует, он будет жаловаться и никогда не заглядывает внутрь ./hello, buddy. Этого легко избежать - используйте кавычки вокруг ваших переменных.

Наконец, имена файлов могут содержать новые строки, поэтому подсчет новых строк в списке имен файлов не будет работать; Вы получите дополнительный счет для каждого имени файла с новой строкой. Чтобы избежать этого, не считайте новые строки в списке файлов; вместо этого подсчитайте переводы строки (или любой другой символ), которые представляют один файл. Вот почему findкоманда просто -exec echo \;и нет -exec echo {} \;. Я хочу напечатать только одну новую строку для подсчета файлов.

Шон Дж. Гофф
источник
1
Почему в мире есть человек, который использует переводы строк в имени файла? Спасибо за ответ.
ShyBoy
1
Я считаю, что имена файлов могут содержать любые символы, кроме / и нулевого символа. dwheeler.com/essays/fixing-unix-linux-filenames.html
Flimm
2
Количество будет включать в себя сам каталог. Если вы хотите исключить это из подсчета, используйте-mindepth 1
toxalot
Вы также можете использовать -printf '\n'вместо -exec echo.
toxalot
1
@toxalot вы можете, если у вас есть находка, которая поддерживает -printf, но не если вы хотите, чтобы она работала, например, на FreeBSD.
Шон Дж. Гофф
6

Предполагая, что вы ищете стандартное решение Linux, сравнительно простой способ добиться этого заключается в find:

find dir1/ dir2/ -maxdepth 1 -type f | wc -l

Где findпересекает две указанные подкаталоги, в -maxdepth1, что предотвращает дальнейшую рекурсию и сообщает только файлы ( -type f), разделенные символами новой строки. Затем результат передается для wcподсчета количества этих строк.

jasonwryan
источник
У меня более 2 dirs ... Как я могу объединить вашу команду с find . -maxdepth 1 -type dвыводом?
ShyBoy
Вы можете (а) включить необходимые переменные в переменную и, find $dirs ...или (б) если они находятся исключительно в одном каталоге более высокого уровня, глобус из этого каталога,find */ ...
jasonwryan
1
Это сообщит о неверных результатах, если любое имя файла содержит символ новой строки.
Шон Дж. Гофф
@Shawn: спасибо. Я думал, что у меня есть имена файлов с пробелами, но не рассматривал новые строки: какие-либо предложения по исправлению?
Джейсонвриан
Добавьте -exec echoк вашей команде поиска - таким образом, оно не отображает имя файла, просто перевод строки.
Шон Дж. Гофф
5

Под «без рекурсии» вы имеете в виду, что если directoryName1есть подкаталоги, то вы не хотите считать файлы в подкаталогах? Если это так, вот способ подсчитать все обычные файлы в указанных каталогах:

count=0
for d in directoryName1 directoryName2; do
  for f in "$d"/* "$d"/.[!.]* "$d"/..?*; do
    if [ -f "$f" ]; then count=$((count+1)); fi
  done
done

Обратите внимание, что -fтест выполняет две функции: он проверяет, является ли запись, совпадающая с одним из указанных выше глобусов, обычным файлом, и проверяет, была ли запись совпадающей (если один из глобусов ничего не соответствует, шаблон остается как есть). Если вы хотите сосчитать все записи в указанных каталогах независимо от их типа, замените -fна -e.

В Ksh есть способ сделать шаблоны соответствующими точечным файлам и создать пустой список в случае, если ни один файл не соответствует шаблону. Таким образом, в ksh вы можете считать обычные файлы следующим образом:

FIGNORE='.?(.)'
count=0
for x in ~(N)directoryName1/* ~(N)directoryName2/*; do
  if [ -f "$x" ]; then ((++count)); fi
done

или все файлы просто так:

FIGNORE='.?(.)'
files=(~(N)directoryName1/* ~(N)directoryName2/*)
count=${#files}

У Bash есть разные способы сделать это проще. Для подсчета обычных файлов:

shopt -s dotglob nullglob
count=0
for x in directoryName1/* directoryName2/*; do
  if [ -f "$x" ]; then ((++count)); fi
done

Для подсчета всех файлов:

shopt -s dotglob nullglob
files=(directoryName1/* directoryName2/*)
count=${#files}

Как обычно, это еще проще в Zsh. Для подсчета обычных файлов:

files=({directoryName1,directoryName2}/*(DN.))
count=$#files

Перейдите (DN.)на (DN)подсчет всех файлов.

¹ Обратите внимание, что каждый шаблон соответствует самому себе, в противном случае результаты могут быть отключены (например, если вы считаете файлы, начинающиеся с цифры, вы не можете просто сделать это, for x in [0-9]*; do if [ -f "$x" ]; then …потому что может быть файл с именем [0-9]foo).

Жиль "ТАК - перестань быть злым"
источник
2

Основываясь на сценарии подсчета , ответе Шона и трюке Bash, чтобы убедиться, что даже имена файлов с символами новой строки напечатаны в удобной форме в одну строку:

for f in *
do
    if [ -d "./$f" ]
    then
        printf %q "$f"
        printf %s ' '
        find "$f" -maxdepth 1 -printf x | wc -c
    fi
done

printf %qзаключается в печати цитируемой версии строки, то есть строки в одну строку, которую вы можете поместить в скрипт Bash, чтобы интерпретировать ее как буквенную строку, включающую (потенциально) переводы строки и другие специальные символы. Например, см. echo -n $'\tfoo\nbar'Против printf %q $'\tfoo\nbar'.

Команда findработает, просто печатая один символ для каждого файла, а затем подсчитывая их вместо подсчета строк.

l0b0
источник
1

Вот «грубая сила» -ish способ получить результат, используя find, echo, ls, wc, xargsи awk.

find . -maxdepth 1 -type d -exec sh -c "echo '{}'; ls -1 '{}' | wc -l" \; | xargs -n 2 | awk '{print $1" "$2}'
Генерал
источник
Эта работа. Но вывод испортился, если у вас есть каталоги, у которых есть `` пробел в имени.
ShyBoy
Это сообщит о неверных результатах, если любое имя файла содержит символ новой строки.
Шон Дж. Гофф
-1
for i in *; do echo $i; ls $i | wc -l; done
Динеш
источник
4
Добро пожаловать в U & L. Ответы должны быть подробными с объяснениями, а не просто пропусками кода. Пожалуйста, раскройте это и объясните, что происходит. Кроме того, это очень неэффективный способ сделать это и не обрабатывать файлы с пробелами, например.
SLM