Existe-t-il une commande bash qui compte les fichiers?

182

Existe-t-il une commande bash qui compte le nombre de fichiers correspondant à un modèle?

Par exemple, je veux obtenir le nombre de tous les fichiers dans un répertoire qui correspondent à ce modèle: log*

hudi
la source

Réponses:

243

Ce simple one-liner devrait fonctionner dans n'importe quel shell, pas seulement bash:

ls -1q log* | wc -l

ls -1q vous donnera une ligne par fichier, même s'ils contiennent des espaces ou des caractères spéciaux tels que des retours à la ligne.

La sortie est dirigée vers wc -l, qui compte le nombre de lignes.

Daniel
la source
10
Je n'utiliserais pas -l, car cela nécessite stat(2)sur chaque fichier et aux fins de comptage n'ajoute rien.
camh
12
Je n'utiliserais pas ls, car il crée un processus enfant. log*est étendu par le shell, non ls, donc un simple echoferait.
cdarke
2
Sauf qu'un écho ne fonctionnera pas si vous avez des noms de fichiers avec des espaces ou des caractères spéciaux.
Daniel
4
@WalterTross C'est vrai (ce n'est pas que l'efficacité était une exigence de la question initiale). Je viens également de découvrir que -q prend en charge les fichiers avec des retours à la ligne, même lorsque la sortie n'est pas le terminal. Et ces indicateurs sont pris en charge par toutes les plates-formes et tous les shells sur lesquels j'ai testé. Mise à jour de la réponse, merci à vous et à camh pour la contribution!
Daniel
3
S'il y a un répertoire appelé logsdans le répertoire en question, alors le contenu de ce répertoire de journaux sera également compté. Ce n'est probablement pas intentionnel.
mogsie
54

Vous pouvez le faire en toute sécurité (c'est-à-dire que vous ne serez pas bogué par des fichiers avec des espaces ou \ndans leur nom) avec bash:

$ shopt -s nullglob
$ logfiles=(*.log)
$ echo ${#logfiles[@]}

Vous devez activer nullglobpour ne pas obtenir le littéral *.logdans le $logfiles tableau si aucun fichier ne correspond. (Voir Comment «annuler» un 'set -x'? Pour des exemples sur la façon de le réinitialiser en toute sécurité.)

Tapis
la source
2
Indiquez peut- être explicitement qu'il s'agit d'une réponse uniquement à Bash , en particulier pour les nouveaux visiteurs qui ne sont pas encore tout à fait au courant de la différence entre sh et bash
tripleee
De plus, la finale shopt -u nullglobdoit être ignorée si elle nullglobn'a pas été désactivée alors que vous avez commencé.
tripleee
Remarque: le remplacement *.logpar just *comptera les répertoires. Si les fichiers que vous souhaitez énumérer ont la convention de dénomination traditionnelle de name.extension, utilisez *.*.
AlainD
52

Beaucoup de réponses ici, mais certaines ne prennent pas en compte

  • noms de fichiers contenant des espaces, des retours à la ligne ou des caractères de contrôle
  • noms de fichiers commençant par des tirets (imaginez un fichier appelé -l)
  • fichiers cachés, qui commencent par un point (si le glob était *.logau lieu delog*
  • répertoires qui correspondent au glob (par exemple, un répertoire appelé logsqui correspond log*)
  • répertoires vides (c'est-à-dire que le résultat est 0)
  • répertoires extrêmement volumineux (les énumérer tous pourrait épuiser la mémoire)

Voici une solution qui les gère tous:

ls 2>/dev/null -Ubad1 -- log* | wc -l

Explication:

  • -Uprovoque lsde ne pas trier les entrées, ce qui signifie qu'il n'a pas besoin de charger la liste complète du répertoire en mémoire
  • -bimprime les échappements de style C pour les caractères non graphiques, ce qui entraîne de manière cruciale l'impression de nouvelles lignes sous forme de \n.
  • -aimprime tous les fichiers, même les fichiers cachés (pas strictement nécessaire lorsque le glob log*n'implique aucun fichier caché)
  • -dimprime les répertoires sans essayer de lister le contenu du répertoire, ce qui lsserait normalement
  • -1 s'assure qu'il est sur une colonne (ls le fait automatiquement lors de l'écriture dans un tube, donc ce n'est pas strictement nécessaire)
  • 2>/dev/nullredirige stderr pour que s'il n'y a aucun fichier journal, ignorez le message d'erreur. (Notez que shopt -s nullglobcela entraînerait la lsliste de l'ensemble du répertoire de travail à la place.)
  • wc -lconsomme la liste des répertoires au fur et à mesure de sa génération, de sorte que la sortie de lsn'est jamais en mémoire à aucun moment.
  • --Les noms de fichiers sont séparés de la commande en utilisant --afin de ne pas être compris comme des arguments de ls(en cas de log*suppression)

La coquille va développer log*à la liste complète des fichiers, ce qui peut épuiser la mémoire si elle est un grand nombre de fichiers, donc il est en cours d' exécution à travers grep mieux:

ls -Uba1 | grep ^log | wc -l

Ce dernier gère des répertoires de fichiers extrêmement volumineux sans utiliser beaucoup de mémoire (bien qu'il utilise un sous-shell). Le -dn'est plus nécessaire, car il ne répertorie que le contenu du répertoire actuel.

mogsie
la source
48

Pour une recherche récursive:

find . -type f -name '*.log' -printf x | wc -c

wc -ccomptera le nombre de caractères dans la sortie de find, tandis que -printf xdit findd'imprimer un seulx pour chaque résultat.

Pour une recherche non récursive, procédez comme suit:

find . -maxdepth 1 -type f -name '*.log' -printf x | wc -c
Will Vousden
la source
6
Même si vous n'avez pas de fichiers avec des espaces, un autre utilisateur de votre script peut rencontrer un fichier nommé de manière malveillante, provoquant l'échec des scripts. De plus, d'autres personnes rencontrant cela sur StackOverflow peuvent avoir des fichiers avec des nouvelles lignes et doivent connaître les pièges.
mogsie
Pour info, si vous omettez simplement, -name '*.log'il comptera tous les fichiers, ce dont j'avais besoin pour mon cas d'utilisation. Le drapeau -maxdepth est également extrêmement utile, merci!
starmandeluxe
2
Cela produit toujours des résultats incorrects s'il existe des noms de fichiers avec des sauts de ligne. La solution de contournement est simple avec find; imprimez simplement autre chose que le nom du fichier textuel.
tripleee
8

La réponse acceptée pour cette question est fausse, mais j'ai un faible représentant, je ne peux donc pas y ajouter de commentaire.

La bonne réponse à cette question est donnée par Mat:

shopt -s nullglob
logfiles=(*.log)
echo ${#logfiles[@]}

Le problème avec la réponse acceptée est que wc -l compte le nombre de caractères de nouvelle ligne, et les compte même s'ils s'impriment sur le terminal sous la forme '?' dans la sortie de 'ls -l'. Cela signifie que la réponse acceptée échoue lorsqu'un nom de fichier contient un caractère de nouvelle ligne. J'ai testé la commande suggérée:

ls -l log* | wc -l

et il signale par erreur une valeur de 2 même s'il n'y a qu'un seul fichier correspondant au modèle dont le nom contient un caractère de nouvelle ligne. Par exemple:

touch log$'\n'def
ls log* -l | wc -l
Dan Yard
la source
6

Si vous avez beaucoup de fichiers et que vous ne voulez pas utiliser la shopt -s nullglobsolution de tableau élégant et bash, vous pouvez utiliser find et ainsi de suite tant que vous n'imprimez pas le nom du fichier (qui peut contenir des nouvelles lignes).

find -maxdepth 1 -name "log*" -not -name ".*" -printf '%i\n' | wc -l

Cela trouvera tous les fichiers qui correspondent au journal * et qui ne commencent pas par .* - Le "not name. *" Est redondant, mais il est important de noter que la valeur par défaut pour "ls" est de ne pas afficher les fichiers à points, mais la valeur par défaut car trouver, c'est les inclure.

C'est une réponse correcte et gère tout type de nom de fichier que vous pouvez lui lancer, car le nom de fichier n'est jamais transmis entre les commandes.

Mais la shopt nullglobréponse est la meilleure!

mogsie
la source
Vous devriez probablement mettre à jour votre réponse originale au lieu de répondre à nouveau.
qodeninja
Je pense que l'utilisation findet l'utilisation lssont deux façons différentes de résoudre le problème. findn'est pas toujours présent sur une machine, mais lsest généralement,
mogsie
2
Mais alors une boîte de saindoux qui n'en a pas n'a findprobablement pas toutes ces options sophistiquées pour l'un lsou l'autre.
tripleee
1
Notez également comment cela s'étend à toute une arborescence de répertoires si vous supprimez le-maxdepth 1
tripleee
1
Notez que cette solution comptera les fichiers dans les répertoires cachés dans son compte. findfait cela par défaut. Cela peut créer de la confusion si l'on ne se rend pas compte qu'il existe un dossier enfant caché, et peut rendre son utilisation avantageuse lsdans certaines circonstances, ce qui ne signale pas les fichiers cachés par défaut.
MrPotatoHead
6

Voici ma seule doublure pour cela.

 file_count=$( shopt -s nullglob ; set -- $directory_to_search_inside/* ; echo $#)
zee
la source
Il m'a fallu quelques recherches sur Google pour comprendre, mais c'est bien! Donc, set -- ne rien faire d'autre que de nous préparer pour $#, qui stocke le nombre d'arguments de ligne de commande qui ont été passés au programme shell
xverges
@xverges Oui, "shopt -s nullglob" sert à ne pas compter les fichiers cachés (.files). set - sert à stocker / définir le nombre de paramètres de position (nombre de fichiers, dans ce cas). et # $ pour afficher le nombre de paramètres positionnels (nombre de fichiers).
zee
3

Vous pouvez utiliser l'option -R pour rechercher les fichiers avec ceux à l'intérieur des répertoires récursifs

ls -R | wc -l // to find all the files

ls -R | grep log | wc -l // to find the files which contains the word log

vous pouvez utiliser des motifs sur le grep

Moh .S
la source
3

Un commentaire important

(pas assez de réputation pour commenter)

C'est BUGGY :

ls -1q some_pattern | wc -l

S'il shopt -s nullglobest défini, il imprime le nombre de TOUS les fichiers normaux, pas seulement ceux avec le modèle (testé sur CentOS-8 et Cygwin). Qui sait quels sont les autres bugs insignifiants ls?

C'est CORRECT et beaucoup plus rapide:

shopt -s nullglob; files=(some_pattern); echo ${#files[@]};

Il fait le travail attendu.


Et les temps de fonctionnement diffèrent.
Le 1er: 0.006sur CentOS, et 0.083sur Cygwin (au cas où il serait utilisé avec précaution).
Le 2ème: 0.000sur CentOS, et 0.003sur Cygwin.

Petit garçon
la source
2

Vous pouvez définir une telle commande facilement, en utilisant une fonction shell. Cette méthode ne nécessite aucun programme externe et ne génère aucun processus enfant. Il n'essaye pas d' lsanalyser les caractères dangereux et gère très bien les caractères «spéciaux» (espaces blancs, nouvelles lignes, barres obliques inverses, etc.). Il ne repose que sur le mécanisme d'extension de nom de fichier fourni par le shell. Il est compatible avec au moins sh, bash et zsh.

La ligne ci-dessous définit une fonction appelée countqui imprime le nombre d'arguments avec lesquels elle a été appelée.

count() { echo $#; }

Appelez-le simplement avec le motif souhaité:

count log*

Pour que le résultat soit correct lorsque le modèle de globbing n'a pas de correspondance, l'option shell nullglob(ou failglob- qui est le comportement par défaut sur zsh) doit être définie au moment de l'expansion. Il peut être défini comme ceci:

shopt -s nullglob    # for sh / bash
setopt nullglob      # for zsh

En fonction de ce que vous voulez compter, vous pourriez également être intéressé par l'option shell dotglob.

Malheureusement, avec bash au moins, il n'est pas facile de définir ces options localement. Si vous ne souhaitez pas les définir globalement, la solution la plus simple consiste à utiliser la fonction de cette manière plus compliquée:

( shopt -s nullglob ; shopt -u failglob ; count log* )

Si vous voulez récupérer la syntaxe légère count log*, ou si vous voulez vraiment éviter de générer un sous-shell, vous pouvez pirater quelque chose du genre:

# sh / bash:
# the alias is expanded before the globbing pattern, so we
# can set required options before the globbing gets expanded,
# and restore them afterwards.
count() {
    eval "$_count_saved_shopts"
    unset _count_saved_shopts
    echo $#
}
alias count='
    _count_saved_shopts="$(shopt -p nullglob failglob)"
    shopt -s nullglob
    shopt -u failglob
    count'

En prime, cette fonction est d'une utilisation plus générale. Par exemple:

count a* b*          # count files which match either a* or b*
count $(jobs -ps)    # count stopped jobs (sh / bash)

En transformant la fonction en un fichier script (ou un programme C équivalent), appelable depuis le PATH, elle peut également être composée avec des programmes tels que findet xargs:

find "$FIND_OPTIONS" -exec count {} \+    # count results of a search
Maëlan
la source
2

J'ai beaucoup réfléchi à cette réponse, surtout compte tenu des choses à ne pas analyser . Au début, j'ai essayé

<ATTENTION! N'A PAS FONCTIONNÉ>
du --inodes --files0-from=<(find . -maxdepth 1 -type f -print0) | awk '{sum+=int($1)}END{print sum}'
</ AVERTISSEMENT! N'A PAS FONCTIONNÉ>

qui fonctionnait s'il n'y avait qu'un nom de fichier comme

touch $'w\nlf.aa'

mais a échoué si j'ai créé un nom de fichier comme celui-ci

touch $'firstline\n3 and some other\n1\n2\texciting\n86stuff.jpg'

J'ai finalement trouvé ce que je mets ci-dessous. Remarque J'essayais d'obtenir un nombre de tous les fichiers dans le répertoire (sans inclure les sous-répertoires). Je le pense, avec les réponses de @Mat et @Dan_Yard, ainsi que d'avoir au moins la plupart des exigences énoncées par @mogsie (je ne suis pas sûr de la mémoire.) Je pense que la réponse de @mogsie est correcte, mais j'essaie toujours de rester à l'écart de l'analyse à lsmoins que ce ne soit une situation extrêmement spécifique.

awk -F"\0" '{print NF-1}' < <(find . -maxdepth 1 -type f -print0) | awk '{sum+=$1}END{print sum}'

Plus lisible:

awk -F"\0" '{print NF-1}' < \
  <(find . -maxdepth 1 -type f -print0) | \
    awk '{sum+=$1}END{print sum}'

Cela fait une recherche spécifiquement pour les fichiers, délimitant la sortie avec un caractère nul (pour éviter les problèmes d'espaces et de sauts de ligne), puis en comptant le nombre de caractères nuls. Le nombre de fichiers sera un de moins que le nombre de caractères nuls, car il y aura un caractère nul à la fin.

Pour répondre à la question du PO, il y a deux cas à considérer

1) Recherche non récursive:

awk -F"\0" '{print NF-1}' < \
  <(find . -maxdepth 1 -type f -name "log*" -print0) | \
    awk '{sum+=$1}END{print sum}'

2) Recherche récursive. Notez que ce qu'il y a à l'intérieur du -nameparamètre peut devoir être changé pour un comportement légèrement différent (fichiers cachés, etc.).

awk -F"\0" '{print NF-1}' < \
  <(find . -type f -name "log*" -print0) | \
    awk '{sum+=$1}END{print sum}'

Si quelqu'un souhaite commenter ces réponses par rapport à celles que j'ai mentionnées dans cette réponse, veuillez le faire.


Remarque, je suis arrivé à ce processus de réflexion en obtenant cette réponse .

bballdave025
la source
1

Voici ce que je fais toujours:

ls log * | awk 'END {print NR}'

Shuang Liang
la source
awk 'END{print NR}'devrait être équivalent à wc -l.
musiphil
0
ls -1 log* | wc -l

Ce qui signifie lister un fichier par ligne, puis le diriger vers la commande de comptage de mots avec le changement de paramètre pour compter les lignes.

nudzo
la source
L'option "-1" n'est pas nécessaire lors du raccordement de la sortie ls. Mais vous voudrez peut-être masquer le message d'erreur ls si aucun fichier ne correspond au modèle. Je suggère "ls log * 2> / dev / null | wc -l".
JohnMudd
La discussion sous la réponse de Daniel est également pertinente ici. Cela fonctionne bien lorsque vous n'avez pas de répertoires ou de noms de fichiers correspondants avec des retours à la ligne, mais une bonne réponse devrait au moins souligner ces conditions aux limites, et une bonne réponse ne devrait pas les avoir. De nombreux bogues sont dus au fait que quelqu'un a copié / collé du code qu'il n'a pas compris; alors souligner les défauts les aide au moins à comprendre ce qu'il faut surveiller. (Certes, beaucoup plus de bugs se produisent parce qu'ils ont ignoré les mises en garde, puis les choses ont changé après avoir pensé que le code était probablement assez bon pour leur objectif.)
tripleee
-1

Pour tout compter, dirigez simplement ls vers la ligne de nombre de mots:

ls | wc -l

Pour compter avec motif, redirigez d'abord vers grep:

ls | grep log | wc -l
jturi
la source