Comment signaler le nombre de fichiers dans tous les sous-répertoires?


24

Je dois inspecter tous les sous-répertoires et signaler le nombre de fichiers (sans autre récursivité) qu'ils contiennent:

directoryName1 numberOfFiles
directoryName2 numberOfFiles

Pourquoi voulez-vous utiliser findquand Bash fera l'affaire? (shopt -s dotglob; for dir in */; do all=("$dir"/*); echo "$dir: ${#all[@]}"; done): pour tous les répertoires, comptez le nombre d'entrées dans ce répertoire (y compris les fichiers de points cachés, à l'exclusion de .et ..)
janmoesen

@janmoesen Pourquoi n'avez-vous pas répondu à cela? Je suis nouveau dans les scripts shell, mais je ne vois aucun problème avec votre méthode. Pour moi, cela ressemble à la meilleure façon. Personne n'a voté pour votre commentaire, mais personne n'a commenté pourquoi il pourrait être mauvais non plus. Les réponses votées ont beaucoup plus de représentants que vous, donc je me demande si je manque quelque chose.
toxalot du

@toxalot: Je n'ai pas pris la peine de l'ajouter comme réponse car elle était si courte (et peut-être légèrement condescendante). N'hésitez pas à voter pour le commentaire. :-) En outre, la question est quelque peu vague quant à ce que signifie "combien de fichiers". Ma solution compte les fichiers et répertoires "normaux" ; peut-être que l'affiche voulait vraiment dire "des fichiers, pas des répertoires". Une autre chose à garder à l'esprit est que cette globalisation ne prend pas en compte les fichiers dot "cachés". Il existe cependant des moyens de contourner ces deux pièges. Mais encore une fois: pas sûr des exigences exactes de l'affiche originale.
janmoesen

Réponses:


30

Cela le fait de manière sûre et portable. Il ne sera pas confondu par des noms de fichiers étranges.

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

Notez qu'il imprimera d'abord le nombre de fichiers, puis le nom du répertoire sur une ligne distincte. Si vous souhaitez conserver le format OP, vous aurez besoin d'un formatage supplémentaire, par exemple

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

Si vous avez un ensemble spécifique de sous-répertoires qui vous intéressent, vous pouvez les remplacer *par eux.

Pourquoi est-ce sûr? (et donc digne d'un script)

Les noms de fichiers peuvent contenir n'importe quel caractère sauf /. Il y a quelques caractères qui sont traités spécialement soit par le shell, soit par les commandes. Il s'agit notamment des espaces, des nouvelles lignes et des tirets.

L'utilisation de la for f in *construction est un moyen sûr d'obtenir chaque nom de fichier, quel qu'il soit.

Une fois que vous avez le nom de fichier dans une variable, vous devez toujours éviter des choses comme find $f . S'il $fcontenait le nom de fichier -test, findse plaindrait de l'option que vous venez de lui donner. La façon d'éviter cela est d'utiliser ./en face du nom; de cette façon, il a la même signification, mais il ne commence plus par un tiret.

Les nouvelles lignes et les espaces sont également un problème. S'il est $fcontenu "bonjour, mon pote" comme nom de fichier find ./$f, c'est find ./hello, buddy. Tu disfind de regarder ./hello,et buddy. Si ceux-ci n'existent pas, il se plaindra et ne cherchera jamais ./hello, buddy. C'est facile à éviter - utilisez des guillemets autour de vos variables.

Enfin, les noms de fichiers peuvent contenir des sauts de ligne, donc compter les sauts de ligne dans une liste de noms de fichiers ne fonctionnera pas; vous obtiendrez un compte supplémentaire pour chaque nom de fichier avec une nouvelle ligne. Pour éviter cela, ne comptez pas les sauts de ligne dans une liste de fichiers; comptez plutôt les sauts de ligne (ou tout autre caractère) qui représentent un seul fichier. C'est pourquoi la findcommande a simplement -exec echo \;et non -exec echo {} \;. Je souhaite uniquement imprimer une nouvelle ligne dans le but de comptabiliser les fichiers.


1
Pourquoi y a-t-il une personne au monde qui utilise des sauts de ligne dans le nom de fichier? Merci d'avoir répondu.
ShyBoy

1
Les noms de fichiers peuvent contenir n'importe quel caractère sauf / et le caractère nul, je crois. dwheeler.com/essays/fixing-unix-linux-filenames.html
Flimm

2
Le décompte inclura le répertoire lui-même. Si vous voulez exclure cela du décompte, utilisez-mindepth 1
toxalot

Vous pouvez également utiliser -printf '\n'au lieu de -exec echo.
toxalot

1
@toxalot vous le pouvez si vous avez une trouvaille qui le supporte -printf, mais pas si vous voulez qu'elle fonctionne sur FreeBSD, par exemple.
Shawn J. Goff

6

En supposant que vous recherchez une solution Linux standard, un moyen relativement simple d'y parvenir est de find:

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

findtraverse les deux sous-répertoires spécifiés, vers un -maxdepthde 1 qui empêche toute récursivité supplémentaire et ne signale que les fichiers ( -type f) séparés par des retours à la ligne. Le résultat est ensuite canalisé pour wccompter le nombre de ces lignes.


J'ai plus de 2 dirs ... Comment puis-je combiner votre commande avec la find . -maxdepth 1 -type dsortie?
ShyBoy

Vous pouvez soit (a) inclure les répertoires requis dans une variable et find $dirs ...ou, (b) s'ils se trouvent exclusivement dans le répertoire d'un niveau supérieur, glob de ce répertoire,find */ ...
jasonwryan

1
Cela signalera des résultats incorrects si un nom de fichier contient un caractère de nouvelle ligne.
Shawn J. Goff

@ Shawn: merci. Je pensais que j'avais des noms de fichiers avec des espaces couverts, mais je n'avais pas envisagé de nouvelles lignes: des suggestions de correction?
jasonwryan

Ajoutez -exec echoà votre commande find - de cette façon, elle ne fait pas écho au nom de fichier, juste une nouvelle ligne.
Shawn J. Goff

5

Par «sans récursivité», voulez-vous dire que si directoryName1 a des sous-répertoires, alors vous ne voulez pas compter les fichiers dans les sous-répertoires? Si oui, voici un moyen de compter tous les fichiers normaux dans les répertoires indiqués:

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

Notez que le -ftest remplit deux fonctions: il teste si l'entrée correspondant à l'un des globs ci-dessus est un fichier normal, et il teste si l'entrée était une correspondance (si l'un des globs ne correspond à rien, le modèle reste tel quel¹). Si vous souhaitez compter toutes les entrées dans les répertoires donnés quel que soit leur type, remplacez-f par -e.

Ksh a un moyen de faire correspondre les motifs aux fichiers de points et de produire une liste vide au cas où aucun fichier ne correspondrait à un motif. Donc, dans ksh, vous pouvez compter des fichiers réguliers comme celui-ci:

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

ou tous les fichiers comme celui-ci:

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

Bash a différentes façons de simplifier cela. Pour compter les fichiers normaux:

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

Pour compter tous les fichiers:

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

Comme d'habitude, c'est encore plus simple dans zsh. Pour compter les fichiers normaux:

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

Changez (DN.)pour (DN)compter tous les fichiers.

¹ Notez que chaque modèle correspond lui-même, sinon les résultats peuvent être désactivés (par exemple, si vous comptez des fichiers qui commencent par un chiffre, vous ne pouvez pas simplement le faire for x in [0-9]*; do if [ -f "$x" ]; then …car il peut y avoir un fichier appelé [0-9]foo).


2

Basé sur un script de comptage , la réponse de Shawn et une astuce Bash pour s'assurer que même les noms de fichiers avec des retours à la ligne sont imprimés sous une forme utilisable sur une seule ligne:

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

printf %qconsiste à imprimer une version entre guillemets d'une chaîne, c'est-à-dire une chaîne à une seule ligne que vous pourriez mettre dans un script Bash pour être interprétée comme une chaîne littérale comprenant (potentiellement) des retours à la ligne et d'autres caractères spéciaux. Par exemple, voir echo -n $'\tfoo\nbar'vs printf %q $'\tfoo\nbar'.

La findcommande fonctionne en imprimant simplement un seul caractère pour chaque fichier, puis en les comptant au lieu de compter les lignes.


1

Voici une façon « force brute » ish pour obtenir votre résultat, en utilisant find, echo, ls, wc, xargset awk.

find . -maxdepth 1 -type d -exec sh -c "echo '{}'; ls -1 '{}' | wc -l" \; | xargs -n 2 | awk '{print $1" "$2}'

Ce travail. Mais la sortie est foirée si vous avez des répertoires qui ont `` de l'espace dans le nom.
ShyBoy

Cela signalera des résultats incorrects si un nom de fichier contient un caractère de nouvelle ligne.
Shawn J. Goff

-1
for i in *; do echo $i; ls $i | wc -l; done

4
Bienvenue chez U&L. Les réponses doivent être longues avec des explications et pas simplement des baisses de code. Veuillez développer ceci et expliquer ce qui se passe. C'est également une façon très inefficace de le faire et ne gère pas les fichiers avec des espaces, par exemple.
slm

En utilisant notre site, vous reconnaissez avoir lu et compris notre politique liée aux cookies et notre politique de confidentialité.
Licensed under cc by-sa 3.0 with attribution required.