Как я могу считать файлы с конкретным расширением и каталоги, в которых они находятся?

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

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

  • Мои имена файлов (включая каталоги) могли бы иметь любые символы; они могут запустить с . или - и имейте пробелы или новые строки.
  • У меня могли бы быть некоторые символьные ссылки, имена которых заканчиваются .c, и символьные ссылки на каталоги. Я не хочу, чтобы символьные ссылки сопровождались или считались, или я, по крайней мере, хочу знать, если и когда они считаются.
  • Структура каталогов имеет много уровней, и высокоуровневый каталог (рабочий каталог) имеет по крайней мере один .c файл в нем.

Я торопливо записал некоторые команды в (Bash) оболочка для подсчета их сам, но я не думаю, что результат точен...

shopt -s dotglob
shopt -s globstar
mkdir out
for d in **/; do
     find "$d" -maxdepth 1 -type f -name "*.c" >> out/$(basename "$d")
done
ls -1Aq out | wc -l
cat out/* | wc -l

Это производит жалобы на неоднозначные перенаправления, файлы промахов в текущем каталоге, и сбивает с толку на специальных символах (например, перенаправленный find вывод печатает новые строки в именах файлов), и пишет целый набор пустых файлов (ой).

Как я могу надежно перечислить мой .c файлы и их содержание каталогов?


В случае, если это помогает, вот некоторые команды для создания тестовой структуры с плохими именами и символьными ссылками:

mkdir -p cfiles/{1..3}/{a..b} && cd cfiles
mkdir space\ d
touch -- i.c -.c bad\ .c 'terrible
.c' not-c .hidden.c
for d in space\ d 1 2 2/{a..b} 3/b; do cp -t "$d" -- *.c; done
ln -s 2 dirlink
ln -s 3/b/i.c filelink.c

В получающейся структуре 7 каталогов содержат .c файлы и 29 регулярных файлов заканчиваются .c (если dotglob выключено, когда команды выполняются) (если я просчитался, сообщите мне). Это числа, которые я хочу.

Не стесняйтесь не использовать этот конкретный тест.

N.B.: Ответы в любой оболочке или другом языке будут тестироваться и цениться мной. Если я должен установить новые пакеты, без проблем. Если Вы знаете решение GUI, я поощряю Вас совместно использовать (но я не мог бы пойти, насколько установить целый DE для тестирования его), :) Я использую ПОМОЩНИКА Ubuntu 17.10.

14
задан 9 April 2018 в 03:21

7 ответов

Я не исследовал вывод с символьными ссылками, но:

find . -type f -iname '*.c' -printf '%h\0' |
  sort -z |
  uniq -zc |
  sed -zr 's/([0-9]) .*/\1 1/' |
  tr '\0' '\n' |
  awk '{f += $1; d += $2} END {print f, d}'
  • find управляйте печатает имя каталога каждого .c файл это находит.
  • sort | uniq -c будет давать нам, сколько файлов находится в каждом каталоге ( sort могло бы быть ненужным здесь, не уверенным),
  • с sed, Я заменяю имя каталога 1, таким образом устраняя все возможные странные символы, только с количеством и 1 оставление
  • включение мне преобразовать в разделенный от новой строки вывод с tr
  • которого я затем подвожу итог с awk, для получения общего количества файлов и количества каталогов, которые содержали те файлы. Отметьте это d вот по существу то же как NR. Я, возможно, опустил вставлять 1 в sed команда, и просто распечатанный NR здесь, но я думаю, что это немного более ясно.

Вплоть до tr, данные NUL-разграничены, безопасны против всех допустимых имен файлов.


С zsh и ударом, можно использовать printf %q получить заключенную в кавычки строку, которая не имела бы новых строк в нем. Так, Вы смогли делать что-то как:

shopt -s globstar dotglob nocaseglob
printf "%q\n" **/*.c | awk -F/ '{NF--; f++} !c[$0]++{d++} END {print f, d}'

Однако даже при том, что ** как предполагается, не расширяется для символьных ссылок на каталоги, я не мог получить желаемый вывод на ударе 4.4.18 (1) (Ubuntu 16.04).

$ shopt -s globstar dotglob nocaseglob
$ printf "%q\n" ./**/*.c | awk -F/ '{NF--; f++} !c[$0]++{d++} END {print f, d}'
34 15
$ echo $BASH_VERSION
4.4.18(1)-release

Но zsh хорошо работал, и команда может быть упрощена:

$ printf "%q\n" ./**/*.c(D.:h) | awk '!c[$0]++ {d++} END {print NR, d}'
29 7

D позволяет этому шарику выбрать точечные файлы, . выбирает регулярные файлы (так, не символьные ссылки), и :h печать только путь к каталогу а не имя файла (как find %h) (См. разделы по Поколению Имени файла и Модификаторам). Таким образом с awk управляют, чтобы мы просто считали количество уникального появления каталогов, и количество строк является количеством файла.

16
ответ дан 23 November 2019 в 02:53

Python имеет os.walk, который делает задачи как это легкими, интуитивными, и автоматически устойчивыми даже перед лицом странных имен файлов, таких как те, которые содержат символы новой строки. Этот сценарий Python 3, который я первоначально отправил в чате, предназначается, чтобы быть выполненным в текущем каталоге (но он не должен быть расположен в текущем каталоге, и можно изменить то, что соединяет его каналом, передает os.walk):

#!/usr/bin/env python3

import os

dc = fc = 0
for _, _, fs in os.walk('.'):
    c = sum(f.endswith('.c') for f in fs)
    if c:
        dc += 1
        fc += c
print(dc, fc)

Это печатает количество каталогов, которые непосредственно содержат по крайней мере один файл, имя которого заканчивается в .c, сопровождаемый пространством, сопровождаемым количеством файлов, имена которых заканчиваются в .c. "Скрытые" файлы - то есть, файлы, имена которых запускаются с .- включены, и скрытые каталоги так же пересечены.

os.walk рекурсивно пересекает иерархию каталогов. Это перечисляет все каталоги, которые рекурсивно доступны от начальной точки, которую Вы даете ему, приводя к информации о каждом из них как кортеж трех значений, root, dirs, files. Для каждого каталога это пересекает к (включая первый, имя которого Вы даете ему):

  • root содержит путь того каталога. Обратите внимание, что это полностью не связано с "корневым каталогом" системы / (и также не связанный с /root) хотя это перешло бы к тем, если Вы запускаете там. В этом случае, root запускается в пути .- т.е. текущий каталог - и идет везде ниже его.
  • dirs содержит список путей всех подкаталогов каталога, имя которого в настоящее время сдерживается root.
  • files содержит список путей всех файлов, которые находятся в каталоге, имя которого в настоящее время сдерживается root но это не самостоятельно каталоги. Обратите внимание, что это включает другие виды файлов, чем регулярные файлы, включая символьные ссылки, но это кажется, что Вы не ожидаете, что любые такие записи закончатся в .c и интересуются наблюдением любого, которые делают.

В этом случае я только должен исследовать третий элемент кортежа, files (который я называю fs в сценарии). Как find команда, Python os.walk пересечения в подкаталоги для меня; единственной вещью, которую я должен осмотреть сам, являются названия файлов, которые содержит каждый из них. В отличие от этого, find команда, тем не менее, os.walk автоматически предоставляет мне список тех имен файлов.

Тот сценарий не переходит по символьным ссылкам. Вы очень, вероятно, не хотите символьных ссылок, сопровождаемых для такой операции, потому что они могли сформировать циклы, и потому что, даже при отсутствии циклов, те же файлы и каталоги могут пересекаться и считаться многократно, если они доступны через различные символьные ссылки.

Если Вы когда-нибудь хотели os.walk для следования за символьными ссылками - который Вы обычно не были бы - затем, можно передать followlinks=true к нему. Таким образом, вместо записи os.walk('.') Вы могли записать os.walk('.', followlinks=true). Я повторяю, что Вы редко хотели бы это, специально для задачи как это, где Вы рекурсивно перечисляете всю структуру каталогов, неважно, насколько большой это, и считающий все файлы в нем, которые отвечают некоторому требованию.

11
ответ дан 23 November 2019 в 02:53

Найдите + Perl:

$ find . -type f -iname '*.c' -printf '%h\0' | 
    perl -0 -ne '$k{$_}++; }{ print scalar keys %k, " $.\n" '
7 29

Объяснение

find команда найдет любые регулярные файлы (так никакие символьные ссылки или каталоги) и затем распечатает название каталога, в котором они находятся (%h) сопровождаемый \0.

  • perl -0 -ne : считайте вход линию за линией (-n) и примените сценарий, данный -e к каждой строке. -0 устанавливает входной разделитель строки на \0 таким образом, мы можем считать разграниченный пустым указателем вход.
  • $k{$_}++ : $_ специальная переменная, которая принимает значение текущей строки. Это используется в качестве ключа к хешу %k, чьи значения являются количеством раз, каждая входная строка (имя каталога) была замечена.
  • }{ : это - краткий способ записать END{}. Любые команды после }{ будет выполняться однажды, после того, как весь вход будет обработан.
  • print scalar keys %k, " $.\n": keys %k возвращает массив ключей в хеше %k. scalar keys %k дает число элементов в том массиве, количестве замеченных каталогов. Это печатается наряду с текущим значением $., специальная переменная, которая содержит текущий входной номер строки. Так как это выполняется в конце, текущий входной номер строки будет количеством последней строки, таким образом, количество строк, замеченных до сих пор.

Вы могли развернуть команду жемчуга до этого для ясности:

find  . -type f -iname '*.c' -printf '%h\0' | 
    perl -0 -e 'while($line = <STDIN>){
                    $dirs{$line}++; 
                    $tot++;
                } 
                $count = scalar keys %dirs; 
                print "$count $tot\n" '
7
ответ дан 23 November 2019 в 02:53

Вот мое предложение:

#!/bin/bash
tempfile=$(mktemp)
find -type f -name "*.c" -prune >$tempfile
grep -c / $tempfile
sed 's_[^/]*$__' $tempfile | sort -u | grep -c /

Этот короткий сценарий создает tempfile, находит каждый файл в и под текущим каталогом, заканчивающимся в .c и написал список к tempfile. grep затем используется для подсчета файлов (после того, Как я могу получить количество файлов в каталоге с помощью командной строки?) дважды: Во второй раз каталоги, которые перечислены многократно, удалены с помощью sort -u после разделения имен файлов от каждого использования строки sed.

Это также работает правильно с новыми строками в именах файлов: grep -c / количества только строки с наклонной чертой и поэтому рассматривают только первую строку многострочного имени файла в списке.

Вывод

$ tree
.
├── 1
│  ├── 1
│  │  ├── test2.c
│  │  └── test.c
│  └── 2
│  └── test.c
└── 2
   ├── 1
   │  └── test.c
   └── 2

$ tempfile=$(mktemp);find -type f -name "*.c" -prune >$tempfile;grep -c / $tempfile;sed 's_[^/]*$__' $tempfile | sort -u | grep -c /
4
3
4
ответ дан 23 November 2019 в 02:53

Маленький сценарий оболочки

Я предлагаю маленький сценарий оболочки удара с двумя основными командными строками (и переменная filetype помочь переключиться для поиска других типов файлов).

Это не ищет или в символьных ссылках, только регулярные файлы.

#!/bin/bash

filetype=c
#filetype=pdf

# count the 'filetype' files

find -type f -name "*.$filetype" -ls|sed 's#.* \./##'|wc -l | tr '\n' ' '

# count directories containing 'filetype' files

find -type d -exec bash -c "ls -AF '{}'|grep -e '\.'${filetype}$ -e '\.'${filetype}'\*'$ > /dev/null && echo '{} contains file(s)'" \;|grep 'contains file(s)$'|wc -l

Подробный сценарий оболочки

Это - более подробная версия, которая также рассматривает символьные ссылки,

#!/bin/bash

filetype=c
#filetype=pdf

# counting the 'filetype' files

echo -n "number of $filetype files in the current directory tree: "
find -type f -name "*.$filetype" -ls|sed 's#.* \./##'|wc -l

echo -n "number of $filetype symbolic links in the current directory tree: "
find -type l -name "*.$filetype" -ls|sed 's#.* \./##'|wc -l
echo -n "number of $filetype normal files in the current directory tree: "
find -type f -name "*.$filetype" -ls|sed 's#.* \./##'|wc -l
echo -n "number of $filetype symbolic links in the current directory tree including linked directories: "
find -L -type f -name "*.$filetype" -ls 2> /tmp/c-counter |sed 's#.* \./##' | wc -l; cat /tmp/c-counter; rm /tmp/c-counter

# list directories with and without 'filetype' files (good for manual checking; comment away after test)
echo '---------- list directories:'
 find    -type d -exec bash -c "ls -AF '{}'|grep -e '\.'${filetype}$ -e '\.'${filetype}'\*'$ > /dev/null && echo '{} contains file(s)' || echo '{} empty'" \;
echo ''
#find -L -type d -exec bash -c "ls -AF '{}'|grep -e '\.'${filetype}$ -e '\.'${filetype}'\*'$ > /dev/null && echo '{} contains file(s)' || echo '{} empty'" \;

# count directories containing 'filetype' files

echo -n "number of directories with $filetype files: "
find -type d -exec bash -c "ls -AF '{}'|grep -e '\.'${filetype}$ -e '\.'${filetype}'\*'$ > /dev/null && echo '{} contains file(s)'" \;|grep 'contains file(s)$'|wc -l

# list and count directories including symbolic links, containing 'filetype' files
echo '---------- list all directories including symbolic links:'
find -L -type d -exec bash -c "ls -AF '{}' |grep -e '\.'${filetype}$ -e '\.'${filetype}'\*'$ > /dev/null && echo '{} contains file(s)' || echo '{} empty'" \;
echo ''
echo -n "number of directories (including symbolic links) with $filetype files: "
find -L -type d -exec bash -c "ls -AF '{}'|grep -e '\.'${filetype}$ -e '\.'${filetype}'\*'$ > /dev/null && echo '{} contains file(s)'" \; 2>/dev/null |grep 'contains file(s)$'|wc -l

# count directories without 'filetype' files (good for checking; comment away after test)

echo -n "number of directories without $filetype files: "
find -type d -exec bash -c "ls -AF '{}'|grep -e '\.'${filetype}$ -e '\.'${filetype}'\*'$ > /dev/null || echo '{} empty'" \;|grep 'empty$'|wc -l

Тест производится

Из короткого сценария оболочки:

$ ./ccntr 
29 7

Из подробного сценария оболочки:

$ LANG=C ./c-counter
number of c files in the current directory tree: 29
number of c symbolic links in the current directory tree: 1
number of c normal files in the current directory tree: 29
number of c symbolic links in the current directory tree including linked directories: 42
find: './cfiles/2/2': Too many levels of symbolic links
find: './cfiles/dirlink/2': Too many levels of symbolic links
---------- list directories:
. empty
./cfiles contains file(s)
./cfiles/2 contains file(s)
./cfiles/2/b contains file(s)
./cfiles/2/a contains file(s)
./cfiles/3 empty
./cfiles/3/b contains file(s)
./cfiles/3/a empty
./cfiles/1 contains file(s)
./cfiles/1/b empty
./cfiles/1/a empty
./cfiles/space d contains file(s)

number of directories with c files: 7
---------- list all directories including symbolic links:
. empty
./cfiles contains file(s)
./cfiles/2 contains file(s)
find: './cfiles/2/2': Too many levels of symbolic links
./cfiles/2/b contains file(s)
./cfiles/2/a contains file(s)
./cfiles/3 empty
./cfiles/3/b contains file(s)
./cfiles/3/a empty
./cfiles/dirlink empty
find: './cfiles/dirlink/2': Too many levels of symbolic links
./cfiles/dirlink/b contains file(s)
./cfiles/dirlink/a contains file(s)
./cfiles/1 contains file(s)
./cfiles/1/b empty
./cfiles/1/a empty
./cfiles/space d contains file(s)

number of directories (including symbolic links) with c files: 9
number of directories without c files: 5
$ 
4
ответ дан 23 November 2019 в 02:53

Простой Perl один лайнер:

perl -MFile::Find=find -le'find(sub{/\.c\z/ and -f and $c{$File::Find::dir}=++$c}, @ARGV); print 0 + keys %c, " $c"' dir1 dir2

Или более простой с find команда:

find dir1 dir2 -type f -name '*.c' -printf '%h\0' | perl -l -0ne'$c{$_}=1}{print 0 + keys %c, " $."'

Если Вы любите играть в гольф и имеете недавний (как меньше, чем старое десятилетие) Perl:

perl -MFile::Find=find -E'find(sub{/\.c$/&&-f&&($c{$File::Find::dir}=++$c)},".");say 0+keys%c," $c"'
find -type f -name '*.c' -printf '%h\0'|perl -0nE'$c{$_}=1}{say 0+keys%c," $."'
4
ответ дан 23 November 2019 в 02:53

Рассмотрите использование locate команда, которая намного быстрее, чем find команда.

Работа данных тестирования

$ sudo updatedb # necessary if files in focus were added `cron` daily.
$ printf "Number Files: " && locate -0r "$PWD.*\.c$" | xargs -0 -I{} sh -c 'test ! -L "$1" && echo "regular file"' _  {} | wc -l &&  printf "Number Dirs.: " && locate -r "$PWD.*\.c$" | sed 's%/[^/]*$%/%' | uniq -cu | wc -l
Number Files: 29
Number Dirs.: 7

Благодаря Muru для его ответа для помощи мне посредством разделения символьных ссылок из файла рассчитывают в ответе Unix & Linux.

Благодаря Terdon для его ответа $PWD (не направленный на меня) в ответе Unix & Linux.


Исходный ответ ниже ссылаемого комментариями

Краткая форма:

$ cd /
$ sudo updatedb
$ printf "Number Files: " && locate -cr "$PWD.*\.c$"
Number Files: 3523
$ printf "Number Dirs.: " && locate -r "$PWD.*\.c$" | sed 's%/[^/]*$%/%' | uniq -c | wc -l 
Number Dirs.: 648
  • sudo updatedb База данных обновления, используемая locate команда, если .c сегодня были созданы файлы или если Вы удалили .c файлы сегодня.
  • locate -cr "$PWD.*\.c$" найдите все .c файлы в текущем каталоге и это - дети ($PWD). Вместо того, чтобы печатать имена файлов и печать рассчитывают с -c аргумент. r указывает regex вместо значения по умолчанию *pattern* соответствие, которое может привести к слишком многим результатам.
  • locate -r "$PWD.*\.c$" | sed 's%/[^/]*$%/%' | uniq -c | wc -l. Найдите все *.c файлы в текущем каталоге и ниже. Удалите имя файла с sed отъезд только имя каталога. Количество количества файлов в каждом использовании каталога uniq -c. Количество количества каталогов с wc -l.

Запустите в текущем каталоге с остроты

$ cd /usr/src
$ printf "Number Files: " && locate -cr "$PWD.*\.c$" &&  printf "Number Dirs.: " && locate -r "$PWD.*\.c$" | sed 's%/[^/]*$%/%' | uniq -c | wc -l
Number Files: 3430
Number Dirs.: 624

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

Подробная форма:

Подробная форма включает время, таким образом, Вы видите сколько быстрее locate закончено find. Даже если необходимо работать sudo updatedb это много раз быстрее, чем сингл find /.

───────────────────────────────────────────────────────────────────────────────────────────
rick@alien:~/Downloads$ sudo time updatedb
0.58user 1.32system 0:03.94elapsed 48%CPU (0avgtext+0avgdata 7568maxresident)k
48inputs+131920outputs (1major+3562minor)pagefaults 0swaps
───────────────────────────────────────────────────────────────────────────────────────────
rick@alien:~/Downloads$ time (printf "Number Files: " && locate -cr $PWD".*\.c$")
Number Files: 3523

real    0m0.775s
user    0m0.766s
sys     0m0.012s
───────────────────────────────────────────────────────────────────────────────────────────
rick@alien:~/Downloads$ time (printf "Number Dirs.: " && locate -r $PWD".*\.c$" | sed 's%/[^/]*$%/%' | uniq -c | wc -l) 
Number Dirs.: 648

real    0m0.778s
user    0m0.788s
sys     0m0.027s
───────────────────────────────────────────────────────────────────────────────────────────

Примечание: Это - все файлы на ВСЕХ дисках и разделах. т.е. мы можем искать команды Windows также:

$ time (printf "Number Files: " && locate *.exe -c)
Number Files: 6541

real    0m0.946s
user    0m0.761s
sys     0m0.060s
───────────────────────────────────────────────────────────────────────────────────────────
rick@alien:~/Downloads$ time (printf "Number Dirs.: " && locate *.exe | sed 's%/[^/]*$%/%' | uniq -c | wc -l) 
Number Dirs.: 3394

real    0m0.942s
user    0m0.803s
sys     0m0.092s

У меня есть три раздела Windows 10 NTFS, автоматически смонтированные в /etc/fstab. Знайте, располагаются, знает все!

Интересное количество:

$ time (printf "Number Files: " && locate / -c &&  printf "Number Dirs.: " && locate / | sed 's%/[^/]*$%/%' | uniq -c | wc -l)
Number Files: 1637135
Number Dirs.: 286705

real    0m15.460s
user    0m13.471s
sys     0m2.786s

Требуется 15 секунд в файлы количества 1,637,135 в 286 705 каталогах. YMMV.

Для подробной разбивки на locate regex команды, обрабатывающие (кажется, не необходим в этом Вопросы и ответы, но используется на всякий случай), читают это: Использование "располагается" в соответствии с некоторым определенным каталогом?

Дополнительное чтение из недавних статей:

2
ответ дан 23 November 2019 в 02:53

Другие вопросы по тегам:

Похожие вопросы: