Я хочу знать, сколько регулярных файлов имеет расширение .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.
Я не исследовал вывод с символьными ссылками, но:
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
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 управляют, чтобы мы просто считали количество уникального появления каталогов, и количество строк является количеством файла.
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)
. Я повторяю, что Вы редко хотели бы это, специально для задачи как это, где Вы рекурсивно перечисляете всю структуру каталогов, неважно, насколько большой это, и считающий все файлы в нем, которые отвечают некоторому требованию.
Найдите + 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" '
Вот мое предложение:
#!/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
Я предлагаю маленький сценарий оболочки удара с двумя основными командными строками (и переменная 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
$
Простой 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," $."'
Рассмотрите использование 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 команды, обрабатывающие (кажется, не необходим в этом Вопросы и ответы, но используется на всякий случай), читают это: Использование "располагается" в соответствии с некоторым определенным каталогом?
Дополнительное чтение из недавних статей: