Find the number of files in a directory Find the number of files in a directory shell shell

Find the number of files in a directory


readdir is not as expensive as you may think. The knack is avoid stat'ing each file, and (optionally) sorting the output of ls.

/bin/ls -1U | wc -l

avoids aliases in your shell, doesn't sort the output, and lists 1 file-per-line (not strictly necessary when piping the output into wc).

The original question can be rephrased as "does the data structure of a directory store a count of the number of entries?", to which the answer is no. There isn't a more efficient way of counting files than readdir(2)/getdents(2).


One can get the number of subdirectories of a given directory without traversing the whole list by stat'ing (stat(1) or stat(2)) the given directory and observing the number of links to that directory. A given directory with N child directories will have a link count of N+2, one link for the ".." entry of each subdirectory, plus two for the "." and ".." entries of the given directory.

However one cannot get the number of all files (whether regular files or subdirectories) without traversing the whole list -- that is correct.

The "/bin/ls -1U" command will not get all entries however. It will get only those directory entries that do not start with the dot (.) character. For example, it would not count the ".profile" file found in many login $HOME directories.

One can use either the "/bin/ls -f" command or the "/bin/ls -Ua" command to avoid the sort and get all entries.

Perhaps unfortunately for your purposes, either the "/bin/ls -f" command or the "/bin/ls -Ua" command will also count the "." and ".." entries that are in each directory. You will have to subtract 2 from the count to avoid counting these two entries, such as in the following:

expr `/bin/ls -f | wc -l` - 2     # Those are back ticks, not single quotes.

The --format=single-column (-1) option is not necessary on the "/bin/ls -Ua" command when piping the "ls" output, as in to "wc" in this case. The "ls" command will automatically write its output in a single column if the output is not a terminal.


The -U option for ls is not in POSIX, and in OS X's ls it has a different meaning from GNU ls, which is that it makes -t and -l use creation times instead of modification times. -f is in POSIX as an XSI extension. The manual of GNU ls describes -f as do not sort, enable -aU, disable -ls --color and -U as do not sort; list entries in directory order.

POSIX describes -f like this:

Force each argument to be interpreted as a directory and list the name found in each slot. This option shall turn off -l, -t, -s, and -r, and shall turn on -a; the order is the order in which entries appear in the directory.

Commands like ls|wc -l give the wrong result when filenames contain newlines.

In zsh you can do something like this:

a=(*(DN));echo ${#a}

D (glob_dots) includes files whose name starts with a period and N (null_glob) causes the command to not result in an error in an empty directory.

Or the same in bash:

shopt -s dotglob nullglob;a=(*);echo ${#a[@]}

If IFS contains ASCII digits, add double quotes around ${#a[@]}. Add shopt -u failglob to ensure that failglob is unset.

A portable option is to use find:

find . ! -name . -prune|grep -c /

grep -c / can be replaced with wc -l if filenames do not contain newlines. ! -name . -prune is a portable alternative to -mindepth 1 -maxdepth 1.

Or here's another alternative that does not usually include files whose name starts with a period:

set -- *;[ -e "$1" ]&&echo "$#"

The command above does however include files whose name starts with a period when an option like dotglob in bash or glob_dots in zsh is set. When * matches no file, the command results in an error in zsh with the default settings.