Réponses:
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.
ls
, car il crée un processus enfant. log*
est étendu par le shell, non ls
, donc un simple echo
ferait.
logs
dans le répertoire en question, alors le contenu de ce répertoire de journaux sera également compté. Ce n'est probablement pas intentionnel.
Vous pouvez le faire en toute sécurité (c'est-à-dire que vous ne serez pas bogué par des fichiers avec des espaces ou \n
dans leur nom) avec bash:
$ shopt -s nullglob
$ logfiles=(*.log)
$ echo ${#logfiles[@]}
Vous devez activer nullglob
pour ne pas obtenir le littéral *.log
dans 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é.)
shopt -u nullglob
doit être ignorée si elle nullglob
n'a pas été désactivée alors que vous avez commencé.
*.log
par just *
comptera les répertoires. Si les fichiers que vous souhaitez énumérer ont la convention de dénomination traditionnelle de name.extension
, utilisez *.*
.
Beaucoup de réponses ici, mais certaines ne prennent pas en compte
-l
)*.log
au lieu delog*
logs
qui correspond log*
)Voici une solution qui les gère tous:
ls 2>/dev/null -Ubad1 -- log* | wc -l
Explication:
-U
provoque ls
de 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-b
imprime 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
.-a
imprime tous les fichiers, même les fichiers cachés (pas strictement nécessaire lorsque le glob log*
n'implique aucun fichier caché)-d
imprime les répertoires sans essayer de lister le contenu du répertoire, ce qui ls
serait 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/null
redirige stderr pour que s'il n'y a aucun fichier journal, ignorez le message d'erreur. (Notez que shopt -s nullglob
cela entraînerait la ls
liste de l'ensemble du répertoire de travail à la place.)wc -l
consomme la liste des répertoires au fur et à mesure de sa génération, de sorte que la sortie de ls
n'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 -d
n'est plus nécessaire, car il ne répertorie que le contenu du répertoire actuel.
Pour une recherche récursive:
find . -type f -name '*.log' -printf x | wc -c
wc -c
comptera le nombre de caractères dans la sortie de find
, tandis que -printf x
dit find
d'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
-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!
find
; imprimez simplement autre chose que le nom du fichier textuel.
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
Si vous avez beaucoup de fichiers et que vous ne voulez pas utiliser la shopt -s nullglob
solution 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 nullglob
réponse est la meilleure!
find
et l'utilisation ls
sont deux façons différentes de résoudre le problème. find
n'est pas toujours présent sur une machine, mais ls
est généralement,
find
probablement pas toutes ces options sophistiquées pour l'un ls
ou l'autre.
-maxdepth 1
find
fait 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 ls
dans certaines circonstances, ce qui ne signale pas les fichiers cachés par défaut.
Voici ma seule doublure pour cela.
file_count=$( shopt -s nullglob ; set -- $directory_to_search_inside/* ; echo $#)
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
(pas assez de réputation pour commenter)
C'est BUGGY :
ls -1q some_pattern | wc -l
S'il shopt -s nullglob
est 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.
0.006
sur CentOS, et 0.083
sur Cygwin (au cas où il serait utilisé avec précaution).
0.000
sur CentOS, et 0.003
sur Cygwin.
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' ls
analyser 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 count
qui 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 find
et xargs
:
find "$FIND_OPTIONS" -exec count {} \+ # count results of a search
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 à ls
moins 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 -name
paramè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 .
Voici ce que je fais toujours:
ls log * | awk 'END {print NR}'
awk 'END{print NR}'
devrait être équivalent à wc -l
.
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.
-l
, car cela nécessitestat(2)
sur chaque fichier et aux fins de comptage n'ajoute rien.