34

I would like a BASH command to list just the count of files in each subdirectory of a directory.

E.g. in directory /tmp there are dir1, dir2, ... I'd like to see :

`dir1` : x files 
`dir2` : x files ...

9 Answers 9

46

Assuming you want a recursive count of files only, not directories and other types, something like this should work:

find . -maxdepth 1 -mindepth 1 -type d | while read dir; do
  printf "%-25.25s : " "$dir"
  find "$dir" -type f | wc -l
done
6
  • Also, I get "find: warning: you have specified the -maxdepth option after a non-option argument -type, but options are not positional (-maxdepth affects tests specified before it as well as those specified after it). Please specify options before other arguments."
    – jldupont
    Sep 14, 2012 at 21:38
  • 2
    Both answers given so far will give incorrect results in the unlikely case that there are files whose names include newline characters. You can handle that with a find ... -print0 | xargs -0 .... Sep 14, 2012 at 21:57
  • @jldupont: move the depth arguments before the ´-type d´, I've edited the answer.
    – Thor
    Sep 14, 2012 at 22:58
  • Yes, and let me add the information that this excellent solution will not take any external variables and thus will work with bash alias!! Nov 25, 2014 at 13:07
  • 1
    Any time you want to use read foo, consider IFS= read -r foo. In many cases the latter is what you really want. This is such case. The improvement does not solve the (already noted) problem with possible newline characters in pathnames. Dec 2, 2021 at 8:59
20

This task fascinated me so much that I wanted to figure out a solution myself. It doesn't even take a while loop and MAY be faster in execution speed. Needless to say, Thor's efforts helped me a lot to understand things in detail.

So here's mine:

find . -maxdepth 1 -mindepth 1 -type d -exec sh -c 'echo "{} : $(find "{}" -type f | wc -l)" file\(s\)' \;

It looks modest for a reason, for it's way more powerful than it looks. :-)

However, should you intend to include this into your .bash_aliases file, it must look like this:

alias somealias='find . -maxdepth 1 -mindepth 1 -type d -exec sh -c '\''echo "{} : $(find "{}" -type f | wc -l)" file\(s\)'\'' \;'

Note the very tricky handling of nested single quotes. And no, it is not possible to use double quotes for the sh -c argument.

3
  • 1
    It is slower as it invokes /bin/sh for each directory. You can check this with strace -fc script. Your version makes about 70% more system-calls. +1 for shorter code :-)
    – Thor
    Oct 31, 2016 at 13:58
  • 1
    inspired by this; sorted by filecount: find . -maxdepth 1 -mindepth 1 -type d -exec sh -c 'echo "$(find "{}" -type f | wc -l)" {}' \; | sort -nr
    – mnagel
    Mar 21, 2017 at 8:44
15
find . -type f | cut -d"/" -f2 | uniq -c

Lists a folders and files in the current folder with a count of files found beneath. Quick and useful IMO. (files show with count 1).

3
  • 2
    How about a little explanation of how it is working ? :) Oct 4, 2018 at 13:32
  • 1
    awesome, thanks! You might want to add | sort -rn to sort subdirs by number of files. Sep 18, 2019 at 8:12
  • Works well except also shows files in the current dir, not just subdirs...
    – rogerdpack
    Apr 2, 2021 at 18:10
2

Using find is definitely the way to go if you want to count recursively, but if you just want a count of the files directly under a certain directory:

ls dir1 | wc -l

4
  • 1
    I don't want to do this for each of the 1000's of directories I've got there...
    – jldupont
    Sep 14, 2012 at 21:37
  • Then use xargs. ls -d */ | xargs -n1 ls | wc -l (Use the answer you accepted if it already works, though! This is just And Now You Know.)
    – jonvuri
    Sep 14, 2012 at 21:41
  • your proposal didn't show up any results in many seconds whereas the answer I accepted did.
    – jldupont
    Sep 15, 2012 at 8:03
  • @jrajav this approach absolutely fails for directories with whitespace in them. This is why find is so important. (let alone -print0 and xargs -0, already pointed out by Scott in the other answer) Nov 25, 2014 at 13:03
1
find . -mindepth 1 -type d -print0 | xargs -0 -I{} sh -c 'printf "%4d : %s\n" "$(find {} -type f | wc -l)" "{}"'

I need often need to count the number of files in my sub-directories and use this command. I prefer the count to appear first.

0

You could use this python code. Boot up the interpreter by running python3 and paste this:

folder_path = '.'
import os, glob
for folder in sorted(glob.glob('{}/*'.format(folder_path))):
    print('{:}: {:>8,}'.format(os.path.split(folder)[-1], len(glob.glob('{}/*'.format(folder)))))

Or a recursive version for nested counts:

import os, glob
def nested_count(folder_path, level=0):
    for folder in sorted(glob.glob('{}/'.format(os.path.join(folder_path, '*')))):
        print('{:}{:}: {:,}'.format('    '*level, os.path.split(os.path.split(folder)[-2])[-1], len(glob.glob(os.path.join(folder, '*')))))
        nested_count(folder, level+1)
nested_count('.')

Example output:

>>> figures: 5
>>> misc: 1
>>> notebooks: 5
>>>     archive: 65
>>>     html: 12
>>>     py: 12
>>>     src: 14
>>> reports: 1
>>>     content: 6
>>> src: 1
>>>     html_download: 1
0

For reliable methods of counting files in a directory, see this answer of mine: What is a reliable code to count files?

In your case we additionally need to iterate over subdirectories. A for loop is good for this:

for d in */; do
    c="$(cd -- "$d" && find . ! -name . -exec printf a \; | wc -c)"
    if [ "$?" -ne 0 ]; then c="?"; fi
    printf '%-25s : %s\n' "${d%/}" "$c"
done

Notes:

  • Normally */ does not match directories with names starting with a dot ("hidden" directories). In Bash shopt -s dotglob changes this.

  • ! -name . is responsible for not counting the respective subdirectory itself.

  • ${d%/} removes trailing /. If we used * instead of */ in the for loop, then there would be nothing to remove, but the loop would iterate over non-directories as well.

  • Double dash (--) is useful in case a name starts with -. If we used ./*/ in the for loop, then there would be no need for --, but you probably would want to remove the leading ./ while printing the output, this would complicate the code.

  • In the output ? appears in case of a problem. Failing cd (probably because of insufficient permissions) is the problem I had in mind. find unable to descend to some (sub-…)subdirectory does not qualify as a problem in this context. If you see a number then it means find found as many files there, so there are at least as many files in the directory. If you see ? then it means find most likely didn't run because cd had failed.

  • See the already linked answer, it will help you tailor the find command to your needs (e.g. non-recursive solution, counting files of a specific type, some optimizations).

  • Multi-byte characters in names will confuse printf and the output may appear misaligned.

  • The format that prints names is %-25s, so any name is printed as-is (plus padding). Newline characters, carriage return characters, escape sequences in names may cause results you don't expect. With printf builtin in Bash use %q instead of %s (it will be %-25q in our case) to mitigate the problem.

-1

What I use... This makes an array of all the subdirectories in the one you give as a parameter. Print the subdirectory and the count of that same subdirectory until all the subdirectories are processed.

#!/bin/bash    
directories=($(/bin/ls -l $1 | /bin/grep "^d" | /usr/bin/awk -F" " '{print $9}'))

for item in ${directories[*]}
    do
        if [ -d "$1$item" ]; then
            echo "$1$item"
            /bin/ls $1$item | /usr/bin/wc -l
        fi
    done
1
  • -1. Do not parse the output of ls. Your code still falls into the Bash pitfall number one; the fact you used an array only obfuscates the problem, bad things may happen when you define the array. Worse, additional bad things may happen when you use ${directories[*]} (it should be "${directories[@]}"). Dec 2, 2021 at 8:44
-1

Output as csv format for Folders :

for f in $(find * -type d); do echo $f,$(ls ./$f | wc -l) ; done

output:

aFolder,30

bFolder,20

cFolder,10
2
  • Why did you post an answer very similar to your other answer and with the same set of flaws? Dec 3, 2021 at 12:56
  • Your answer could be improved with additional supporting information. Please edit to add further details, such as citations or documentation, so that others can confirm that your answer is correct. You can find more information on how to write good answers in the help center.
    – Community Bot
    Dec 3, 2021 at 13:24

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .