Existe-t-il une alternative plus succincte à la tuyauterie vers wc pour compter les fichiers dans un répertoire

12

Si je le fais ls -1 target_dir | wc -l, j'obtiens un nombre de fichiers dans un répertoire. Je trouve cela un peu lourd. Existe-t-il une manière plus élégante ou succincte?

codecowboy
la source
2
Vous n'avez pas besoin du "-1" lors de la connexion au WC.
Steve
lsdonne déjà le nombre total, alors pourquoi pas ls -l | head -1? Faites-en un alias si vous voulez quelque chose de plus court.
Daniel Wagner
2
@DanielWagner La sortie "total: nnn" de ls -lindique la taille totale des fichiers, pas le nombre de fichiers.
David Richerby
2
N'oubliez pas que ls | wc -lcela vous donnera le mauvais nombre si des noms de fichiers contiennent des retours à la ligne.
chepner
Cela dépend du système de fichiers et compte les répertoires + 2 dans un répertoire. La réponse a 2 supplémentaires (car elle compte elle-même et son parent). stat -c %h .donne les mêmes informations quels -ld . | cut -d" " -f 2
ctrl-alt-delor

Réponses:

12

En supposant que bash 4+ (que possède toute version prise en charge d'Ubuntu):

num_files() (
    shopt -s nullglob
    cd -P -- "${1-.}" || return
    set -- *
    echo "$#"
)

Appelez-le comme num_files [dir]. direst facultatif, sinon il utilise le répertoire courant. Votre version d'origine ne compte pas les fichiers cachés, donc cela non plus. Si vous le voulez, shopt -s dotglobavant set -- *.

Votre exemple d'origine compte non seulement les fichiers normaux, mais aussi les répertoires et autres périphériques - si vous ne voulez vraiment que des fichiers normaux (y compris des liens symboliques vers des fichiers normaux), vous devrez les vérifier:

num_files() (
    local count=0

    shopt -s nullglob
    cd -P -- "${1-.}" || return
    for file in *; do
        [[ -f $file ]] && let count++
    done
    echo "$count"
)

Si vous avez GNU find, quelque chose comme ça est également une option (notez que cela inclut les fichiers cachés, ce que votre commande d'origine n'a pas fait):

num_files() {
    find "${1-.}" -maxdepth 1 -type f -printf x | wc -c
}

(changement -typede -xtypesi vous voulez aussi compter les liens symboliques vers des fichiers réguliers).

Chris Down
la source
N'échouera pas sets'il y a beaucoup de fichiers? Je pense que vous pourriez avoir à utiliser xargset un code de sommation pour que cela fonctionne dans le cas général.
l0b0
1
Aussi, shopt -s dotglobsi vous voulez que les fichiers commençant par .soient comptés
Digital Trauma
1
@ l0b0 Je ne pense pas que setcela échouera dans ces circonstances, car nous ne faisons pas réellement de exec. À savoir, sur mon système, getconf ARG_MAXdonne 262144, mais si je le fais test_arg_max() { set -- {1..262145}; echo $#; }; test_arg_max, il répond volontiers 262145.
kojiro
@DavidRicherby -maxdepthn'est pas POSIX.
Chris Down
4
@MichaelMartinez L'écriture de code évident ne remplace pas l'écriture de code correct.
Chris Down
3

f=(target_dir/*);echo ${#f[*]}

fonctionne correctement pour le fichier avec des espaces, des retours à la ligne, etc. dans le nom.

Aaron Davies
la source
pouvez-vous fournir un certain contexte? Cela devrait-il aller dans un script bash?
codecowboy
ça pourrait. vous pouvez également le mettre directement dans la coque. cette version supposait que vous vouliez le répertoire courant; je l'ai édité donc c'est plus proche de votre question. Fondamentalement, il crée une variable de tableau shell contenant tous les fichiers du répertoire, puis affiche le nombre de ce tableau. devrait fonctionner dans n'importe quel shell avec des tableaux - bash, ksh, zsh, etc. - mais probablement pas sh / ash / dash ordinaire.
Aaron Davies
2

lsest multi-colonnes uniquement s'il sort directement sur un terminal, vous pouvez supprimer l'option "-1", vous pouvez supprimer l' wcoption "-l", ne lire que la première valeur (solution paresseuse, ne pas utiliser pour les preuves légitimes, enquêtes criminelles, missions critiques, opérations tactiques ..).

ls target | wc 
Emmanuel
la source
5
Cela échoue pour les noms de fichiers contenant des sauts de ligne.
l0b0
@Emmanuel Vous aurez besoin d'analyser le résultat de votre wcpour obtenir le nombre de fichiers même dans le cas trivial, alors comment est-ce même une solution?
l0b0
@Emmanuel Cela peut échouer s'il targets'agit d'un glob qui, une fois développé, inclut des éléments commençant par des tirets. Par exemple, créez un nouveau répertoire, allez-y et faites touch -- {1,2,3,-a}.txt && ls *|wc(NB: utilisez rm -- *.txtpour supprimer ces fichiers.)
David Richerby
Vous vouliez dire wc -l? Sinon, vous obtenez le nombre de sauts de ligne, de mots et d'octets de la lssortie. C'est ce que David Richerby a dit: Vous devez à nouveau l'analyser.
erik
@erik Je dis wcsans argument que vous n'avez pas besoin d'analyser si votre cerveau sait que le premier argument est un retour à la ligne.
Emmanuel
2

Si ce que vous êtes de succinctness après (plutôt que la correction exacte à laquelle le traitement des dossiers avec des sauts de ligne dans leurs noms, etc.), je recommande simplement d' aliasing wc -là lc( « nombre de lignes »):

$ alias lc='wc -l'
$ ls target_dir|lc

Comme d'autres l'ont remarqué, vous n'avez pas besoin de cette -1option ls, car elle est automatique lors de l' lsécriture dans un tuyau. (Sauf si vous avez un lsalias pour toujours utiliser le mode colonne. J'ai déjà vu cela, mais pas très souvent.)

Un lcalias est assez pratique en général, et pour cette question, si vous regardez le cas "compter le répertoire courant", il ls|lcest aussi succinct que possible.

Aaron Davies
la source
2

Jusqu'à présent, Aaron est la seule approche plus succincte que la vôtre. Une version plus correcte de votre approche pourrait ressembler à ceci:

ls -aR1q | grep -Ecv '^\./|/$|^$'

Cela répertorie récursivement tous les fichiers - pas les répertoires - un par ligne, y compris .dotfiles sous le répertoire actuel, en utilisant des globes shell au besoin pour remplacer les caractères non imprimables. grep filtre toutes les listes de répertoires parents ou .. ou * / ou les lignes vides - il ne doit donc y avoir qu'une seule ligne par fichier - le nombre total dont grep vous renvoie. Si vous souhaitez également inclure les répertoires enfants:

ls -aR1q | grep -Ecv '^\.{1,2}/|^$'

Supprimez le -Rdans les deux cas si vous ne souhaitez pas de résultats récursifs.

mikeserv
la source
1
J'ai tendance à préférer faire ce genre de choses avec find. Si vous ne voulez qu'un décompte, cela devrait fonctionner: find -mindepth 1 -maxdepth 1 -printf '\n'|wc -l(supprimez les contrôles de profondeur pour obtenir des résultats récursifs).
Aaron Davies
@AaronDavies - cela ne fonctionne pas vraiment. Mettez une nouvelle ligne dans l'un de ces noms de fichiers et voyez par vous-même. Aussi, pour faire la même chose que vous portez: find . \! -name . -prune | wc -l- ce qui ne fonctionne toujours pas, bien sûr.
mikeserv
1
Je ne suis pas - l' printfinstruction affiche une chaîne constante (une nouvelle ligne) qui n'inclut pas du tout le nom de fichier, donc les résultats sont indépendants de tout nom de fichier étrange. Cette astuce ne peut pas être faite du tout avec un findqui ne prend pas en charge printf, bien sûr.
Aaron Davies
@AaronDavies - oh, c'est vrai. J'ai supposé que le nom de fichier était inclus. Cela peut être fait de manière portative, bien sûr:find .//. \!. -name . -prune | grep -c '^\.//\.'
mikeserv
brillant! /étant le seul autre caractère qui ne peut pas apparaître dans les noms de fichiers, la .//.séquence est garantie d'apparaître exactement une fois pour chaque fichier, non? quelques questions cependant - pourquoi .//.et pourquoi -prune? quand cela différerait-il find . \! -name . | grep -c '^\.'? (Je suppose que le .dans votre \!.est une faute de frappe.)
Aaron Davies