What is the Best Way to Count Files in a Directory?

Channel: linux
Abstract: [me@linux ~]$ find tmp -type f | wc -l[me@linux ~]$ ls -a tmp/ | wc -l

There are multiple ways on how to count the number of files in a directory on Linux. Some are less reliable than others and may even lead to an inacurate count of files. This post covers the pitfalls and best practices for three different approaches using ls, find, and the bash shell using globbing and a bash array.

How to count files using the ls and wc commands?

Most solutions you will find online will try to parse a ls command output which is piped through the wc command line, sometimes adding a grep in the mix. A common example is ls -la | wc -l. This approach is not reliable and should be avoided.

Besides the two forked processes for ls and wc, the ls output should never be parsed as a file name may contain a newline character which would lead to bad results when using ls and wc. In that example, the count will also be incorrect as it would include the . (current directory) and .. (parent directory) references.

A better version would be to use ls -1bA | wc -l which would skip the . and .. from the ls output, escape non-printable characters, and force the output to be one entry per line.

[me@linux ~]$ ls -a tmp/
.     ..    .one  file1 file2
[me@linux ~]$ ls -a tmp/ | wc -l
5
[me@linux ~]$ ls -1bA tmp/*
tmp/.one
tmp/file1
tmp/file2
[me@linux ~]$ ls -1bA | wc -l
3
How to count files using the find and wc commands?

Another solution, similar to the previous one, is to use the find command line with wc, example: find tmp/ -type f | wc -l.

This approach will suffer from a similar pitfall as the ls one since file names may contain non-printable characters, including newlines. The output may not be reliably parsed. To overcome this, we can use the printf option of the GNU find utility. Note, that this is not a standard POSIX option.

By using printf we can skip the filename output and replace it with a dot (or any other single character) for each file found, then only count the total characters using wc -c, example: find tmp -type f -printf '.' | wc -c.

? To install GNU Find on macOS, you can use the homebrew package manager with the command brew install findutils. The find command will be installed as gfind by default.

[me@linux ~]$ find tmp -type f
tmp/file2
tmp/file1
tmp/.one
[me@linux ~]$ find tmp -type f | wc -l
3
[me@linux ~]$ find tmp -type f -printf '.'
...
[me@linux ~]$ find tmp -type f -printf '.' | wc -c
3
How to count files using the Bash shell only?

You don’t need to use other command-line tools to count the number of files in a directory in bash. Instead of the previous solutions, without extra fork(), you can use bash globbing with the nullglob and dotglob options with a bash array and optionally a bash if statement.

  • The nullglob option will expand to a null string (empty value) when no files match the filename patterns.
  • The dotglob option is used to also list hidden files starting with a dot (.). You can skip this option if you only want to count files that don’t start with a dot.

Make sure to set (using shopt -s nullglob dotglob) and unset (using shopt -u nullglob dotglob) the options before and after your test as it may impact the rest of your script behavior, especially with the nullglob option.

[me@linux ~]$ shopt -s nullglob dotglob
[me@linux ~]$ matched_files=(tmp/*)
[me@linux ~]$ if ! (( ${#matched_files[*]} )); then
  echo "Directory is empty"
else
  echo "The directory contains ${#matched_files[@]} items which include files, symlink, directories, etc."
fi

# Example Output: The directory contains 3 items which include files, symlink, directories, etc.
[me@linux ~]$ shopt -u nullglob dotglob

You can optionally set the shopt in a subshell to ensure that the rest of your script is not impacted, at the cost of an additional fork(). Below is an example with a bash subshell to check if a directory is empty. Remember that you cannot access variables defined in a subshell, so the use cases may be limited or less readable.

[me@linux ~]$ if (shopt -s nullglob dotglob; files=(tmp/*); ((${#files[@]}))); then
  echo "The directory is not empty";
fi
The directory is not empty

Ref From: shell-tips

Related articles