Je veux savoir combien de fichiers normaux portent l'extension .c
dans une grande structure de répertoires complexe, et combien de répertoires ces fichiers sont répartis. La sortie que je veux est juste ces deux chiffres.
J'ai vu cette question sur la façon d'obtenir le nombre de fichiers, mais j'ai besoin de connaître le nombre de répertoires dans lesquels se trouvent les fichiers.
.
ou -
et avoir des espaces ou des nouvelles lignes..c
et des liens symboliques vers des répertoires. Je ne veux pas que les liens symboliques soient suivis ou comptés, ou du moins que je sache si et quand ils sont comptés..c
.J'ai à la hâte écrit quelques commandes dans (Bash) Shell pour les compter moi-même, mais je ne pense pas que le résultat soit précis ...
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
Cela génère des réclamations concernant des redirections ambiguës, des fichiers manquants dans le répertoire en cours, des déclenchements de caractères spéciaux (par exemple, la sortie redirigée find
___ imprime des nouvelles lignes dans les noms de fichiers ) et écrit tout un tas de fichiers vides (oops) .
Comment puis-je énumérer de manière fiable mes fichiers .c
et leurs répertoires?
Au cas où cela vous aiderait, voici quelques commandes pour créer une structure de test avec des noms et des liens symboliques incorrects:
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
Dans la structure résultante, 7 répertoires contiennent des fichiers .c
et 29 fichiers normaux se terminent par .c
(si dotglob
est désactivé lors de l'exécution des commandes) (si j'ai mal compté, merci de me le faire savoir). Ce sont les chiffres que je veux.
S'il vous plaît n'hésitez pas pas à utiliser ce test particulier.
NB: Les réponses dans n'importe quel shell ou autre langue seront testées et appréciées par moi. Si je dois installer de nouveaux paquets, pas de problème. Si vous connaissez une solution graphique, je vous encourage à partager (mais je n'irais peut-être pas jusqu'à installer un DE complet pour le tester) :) J'utilise Ubuntu MATE 17.10.
Je n'ai pas examiné la sortie avec des liens symboliques mais:
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
imprime le nom du répertoire de chaque fichier .c
trouvé.sort | uniq -c
nous indiquera le nombre de fichiers contenus dans chaque répertoire (le sort
pourrait être inutile ici, pas sûr)sed
name__, je remplace le nom du répertoire par 1
, éliminant ainsi tous les caractères étranges possibles, avec seulement le nombre et le 1
restanttr
name__d
est essentiellement identique à NR
name__. J'aurais pu omettre d'insérer 1
dans la commande sed
et juste d'imprimer NR
ici, mais je pense que cela est un peu plus clair.Jusqu'au tr
name__, les données sont délimitées par NUL, elles sont protégées contre tous les noms de fichiers valides.
Avec zsh et bash, vous pouvez utiliser printf %q
pour obtenir une chaîne entre guillemets, qui ne contiendrait pas de nouvelles lignes. Donc, vous pourriez être capable de faire quelque chose comme:
shopt -s globstar dotglob nocaseglob
printf "%q\n" **/*.c | awk -F/ '{NF--; f++} !c[$0]++{d++} END {print f, d}'
Cependant, même si **
n'est pas censé développer les liens symboliques vers les répertoires , je n'ai pas pu obtenir le résultat souhaité sous bash 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
Mais zsh a bien fonctionné et la commande peut être simplifiée:
$ printf "%q\n" ./**/*.c(D.:h) | awk '!c[$0]++ {d++} END {print NR, d}'
29 7
D
permet à ce glob de sélectionner les fichiers de points, .
sélectionne les fichiers normaux (donc, pas de liens symboliques) et :h
imprime uniquement le chemin du répertoire et non le nom de fichier (comme find
name __'s %h
) (Voir les sections sur Filename GenerationModificateurs ). Donc, avec la commande awk, nous avons juste besoin de compter le nombre de répertoires uniques qui apparaissent, et le nombre de lignes est le nombre de fichiers.
Python a os.walk
, ce qui rend les tâches de ce type faciles, intuitives et automatiquement robustes, même face à des noms de fichiers étranges tels que ceux contenant des caractères de nouvelle ligne. Ce script Python 3, que j'avais posté à l'origine dans le chat , est destiné à être exécuté dans le répertoire actuel (mais it n'a pas à le faire être situé dans le répertoire en cours, et vous pouvez changer le chemin qu'il passe à 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)
Cela imprime le nombre de répertoires contenant directement au moins un fichier dont le nom se termine par .c
, suivi d'un espace, suivi du nombre de fichiers dont le nom se termine par .c
. Les fichiers "cachés", c'est-à-dire ceux dont le nom commence par .
--, sont inclus et les répertoires cachés sont parcourus de la même façon.
os.walk
parcourt de manière récursive une hiérarchie de répertoires. Il énumère tous les répertoires accessibles de manière récursive à partir du point de départ indiqué, générant des informations sur chacun d'eux sous la forme d'un tuple de trois valeurs, root, dirs, files
. Pour chaque répertoire qu'il parcourt (y compris le premier dont vous lui donnez le nom):
root
contient le chemin d'accès de ce répertoire. Notez que cela n’a aucun rapport avec le "répertoire racine" du système /
(et n’a pas non plus de relation avec /root
), même s’il voudrait aller à ceux-là si vous commencez par là. Dans ce cas, root
commence par le chemin .
--, c’est-à-dire le répertoire en cours, et passe partout en dessous.dirs
contient la liste des chemins d'accès de tous les sous-répertoires du répertoire dont le nom est actuellement conservé dans root
name__.files
contient la liste des chemins d'accès de tous les fichiers qui résident dans le répertoire dont le nom est actuellement conservé dans root
mais qui ne sont pas eux-mêmes des répertoires. Notez que cela inclut d'autres types de fichiers que les fichiers normaux, y compris les liens symboliques, mais il semble que vous ne vous attendiez pas à ce que ces entrées se terminent par .c
et que vous êtes intéressé à en voir une.Dans ce cas, je n'ai qu'à examiner le troisième élément du nuplet, files
(que j'appelle fs
dans le script). À l'instar de la commande find
name__, le os.walk
de Python se déplace dans les sous-répertoires pour moi; la seule chose que je doive vérifier moi-même est le nom des fichiers qu’ils contiennent. Contrairement à la commande find
name__, os.walk
me fournit automatiquement une liste de ces noms de fichiers.
Ce script ne suit pas les liens symboliques. Vous avez très probablement ne pas vouloir que les liens symboliques soient suivis pour une telle opération, car ils pourraient: former des cycles, et parce que même s'il n'y a pas de cycles, les mêmes fichiers et répertoires peuvent être parcourus et comptés plusieurs fois s'ils sont accessibles via différents liens symboliques.
Si vous avez déjà voulu que os.walk
suive des liens symboliques - ce que vous ne feriez pas d'habitude - vous pouvez alors lui passer followlinks=true
. C'est-à-dire qu'au lieu d'écrire os.walk('.')
, vous pouvez écrire os.walk('.', followlinks=true)
. Je répète que vous souhaiteriez rarement cela, en particulier pour une tâche telle que celle-ci énumérant de manière récursive une structure de répertoires entière, quelle que soit sa taille, et en comptant tous les fichiers qui répondent à certaines exigences.
Trouver + Perl:
$ find . -type f -iname '*.c' -printf '%h\0' |
Perl -0 -ne '$k{$_}++; }{ print scalar keys %k, " $.\n" '
7 29
La commande find
trouvera tous les fichiers normaux (donc pas de liens symboliques ni de répertoires), puis imprimera le nom du répertoire dans lequel ils se trouvent (%h
) suivi de \0
.
Perl -0 -ne
: lit ligne par ligne (-n
) et applique le script donné par -e
à chaque ligne. Le -0
définit le séparateur de ligne en entrée sur \0
afin que nous puissions lire les entrées délimitées par un caractère nul.$k{$_}++
: $_
est une variable spéciale prenant la valeur de la ligne en cours. Ceci est utilisé comme clé de hash%k
, dont les valeurs correspondent au nombre de fois que chaque ligne d'entrée (nom de répertoire) a été vue.}{
: il s'agit d'une manière abrégée d'écrire END{}
. Toute commande après le }{
sera exécutée une fois, après que toutes les entrées aient été traitées.print scalar keys %k, " $.\n"
: keys %k
renvoie un tableau des clés dans le hachage %k
. scalar keys %k
donne le nombre d'éléments dans ce tableau, le nombre de répertoires vus. Ceci est imprimé avec la valeur actuelle de $.
, une variable spéciale qui contient le numéro de ligne actuel. Comme cela se passe à la fin, le numéro de ligne actuel sera le numéro de la dernière ligne, donc le nombre de lignes vues jusqu'à présent.Vous pouvez développer la commande Perl à ceci, pour plus de clarté:
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" '
Voici ma suggestion:
#!/bin/bash
tempfile=$(mktemp)
find -type f -name "*.c" -Prune >$tempfile
grep -c / $tempfile
sed 's_[^/]*$__' $tempfile | sort -u | grep -c /
Ce court script crée un fichier temporaire, trouve tous les fichiers dans et sous le répertoire en cours se terminant par .c
et écrit la liste dans le fichier temporaire. grep
est ensuite utilisé pour compter les fichiers (après Comment puis-je obtenir un nombre de fichiers dans un répertoire à l'aide de la ligne de commande? ) deux fois: la deuxième fois, les répertoires répertoriés plusieurs fois sont supprimés à l'aide de sort -u
après avoir enlevé les noms de fichiers de chaque ligne en utilisant sed
.
Cela fonctionne également correctement avec les nouvelles lignes dans les noms de fichiers: grep -c /
ne compte que les lignes avec une barre oblique et considère par conséquent uniquement la première ligne d'un nom de fichier multiligne dans la liste.
$ 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
Je suggère un petit script shellh bash avec deux lignes de commande principales (et une variable filetype
pour faciliter les permutations afin de rechercher d'autres types de fichiers).
Il ne cherche pas ou dans des liens symboliques, seulement des fichiers normaux.
#!/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
Ceci est une version plus détaillée qui considère également les liens symboliques,
#!/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
De shellscript court:
$ ./ccntr
29 7
De shellscript verbose:
$ 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
$
Simple Perl one liner:
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
Ou plus simple avec la commande find
:
find dir1 dir2 -type f -name '*.c' -printf '%h\0' | Perl -l -0ne'$c{$_}=1}{print 0 + keys %c, " $."'
Si vous aimez le golf et avez récemment (comme moins de dix ans) 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," $."'
Pensez à utiliser la commande locate
qui est beaucoup plus rapide que la commande find
name__.
$ 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
Merci à Muru pour sa réponse qui m'a aidé à éliminer les liens symboliques du nombre de fichiers dans réponse Unix et Linux .
Merci à Terdon pour sa réponse de $PWD
(qui ne m'est pas adressée) dans réponse Unix et 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
Mettre à jour la base de données utilisée par la commande locate
si les fichiers .c
ont été créés aujourd'hui ou si vous avez supprimé les fichiers .c
aujourd'hui.locate -cr "$PWD.*\.c$"
localisez tous les fichiers .c
du répertoire en cours et ses enfants ($PWD
). Au lieu d’imprimer les noms de fichiers, imprimez le nombre avec l’argument -c
. r
spécifie une expression rationnelle au lieu de la correspondance par défaut *pattern*
, ce qui peut générer trop de résultats.locate -r "$PWD.*\.c$" | sed 's%/[^/]*$%/%' | uniq -c | wc -l
. Localisez tous les fichiers *.c
dans le répertoire en cours et au-dessous. Supprimez le nom du fichier avec sed
en ne laissant que le nom du répertoire. Compter le nombre de fichiers dans chaque répertoire en utilisant uniq -c
. Compter le nombre de répertoires avec 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
Notez comment le nombre de fichiers et le nombre de répertoires ont changé. Je crois que tous les utilisateurs ont le répertoire /usr/src
et peuvent exécuter des commandes ci-dessus avec des nombres différents en fonction du nombre de noyaux installés.
La forme longue inclut le temps afin que vous puissiez voir combien locate
est plus rapide que find
name__. Même si vous devez exécuter Sudo updatedb
, il est plusieurs fois plus rapide qu'un seul 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
───────────────────────────────────────────────────────────────────────────────────────────
Remarque: Il s'agit de tous les fichiers sur DE _ TOUS lecteurs et partitions. Nous pouvons aussi rechercher des commandes 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
J'ai trois partitions Windows 10 NTFS montées automatiquement dans /etc/fstab
. Sachez que localiser sait tout!
$ 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
Il faut 15 secondes pour compter 1 637 135 fichiers dans 286 705 répertoires. YMMV.
Pour une ventilation détaillée de la gestion des regex de la commande locate
(ne semble pas nécessaire dans ce Q & A, mais utilisée au cas où), veuillez lire ceci: tilisez "localize" dans un répertoire spécifique?
Lectures supplémentaires d'articles récents: