Is there a bash command which counts files?
Lots of answers here, but some don't take into account
- file names with spaces, newlines, or control characters in them
- file names that start with hyphens (imagine a file called
-l
) - hidden files, that start with a dot (if the glob was
*.log
instead oflog*
- directories that match the glob (e.g. a directory called
logs
that matcheslog*
) - empty directories (i.e. the result is 0)
- extremely large directories (listing them all could exhaust memory)
Here's a solution that handles all of them:
ls 2>/dev/null -Ubad1 -- log* | wc -l
Explanation:
-U
causesls
to not sort the entries, meaning it doesn't need to load the entire directory listing in memory-b
prints C-style escapes for nongraphic characters, crucially causing newlines to be printed as\n
.-a
prints out all files, even hidden files (not strictly needed when the globlog*
implies no hidden files)-d
prints out directories without attempting to list the contents of the directory, which is whatls
normally would do-1
makes sure that it's on one column (ls does this automatically when writing to a pipe, so it's not strictly necessary)2>/dev/null
redirects stderr so that if there are 0 log files, ignore the error message. (Note thatshopt -s nullglob
would causels
to list the entire working directory instead.)wc -l
consumes the directory listing as it's being generated, so the output ofls
is never in memory at any point in time.--
File names are separated from the command using--
so as not to be understood as arguments tols
(in caselog*
is removed)
The shell will expand log*
to the full list of files, which may exhaust memory if it's a lot of files, so then running it through grep is be better:
ls -Uba1 | grep ^log | wc -l
This last one handles extremely large directories of files without using a lot of memory (albeit it does use a subshell). The -d
is no longer necessary, because it's only listing the contents of the current directory.
For a recursive search:
find . -type f -name '*.log' -printf x | wc -c
wc -c
will count the number of characters in the output of find
, while -printf x
tells find
to print a single x
for each result. This avoids any problems with files with odd names which contain newlines etc.
For a non-recursive search, do this:
find . -maxdepth 1 -type f -name '*.log' -printf x | wc -c
This simple one-liner should work in any shell, not just bash:
ls -1q log* | wc -l
ls -1q will give you one line per file, even if they contain whitespace or special characters such as newlines.
The output is piped to wc -l, which counts the number of lines.
You can do this safely (i.e. won't be bugged by files with spaces or \n
in their name) with bash:
$ shopt -s nullglob
$ logfiles=(*.log)
$ echo ${#logfiles[@]}
You need to enable nullglob
so that you don't get the literal *.log
in the $logfiles
array if no files match. (See How to "undo" a 'set -x'? for examples of how to safely reset it.)