How to use wc and piping to find how many files and directories are in a certain directory?

One approach would be to make use of ls to give us a list of the files, but we want this list to be guaranteed to show only 1 file or directory per line. The -1 switch will do this for us.

$ ls -1
dir1
dir2
dir3
fileA
fileB
fileC

Example

Create the above sample data in an empty directory.

$ mkdir dir{1..3}
$ touch file{A..C}

Check it:

$ ls
dir1  dir2  dir3  fileA  fileB  fileC

Now to count you can use wc -l to count the number of lines, which correspond to a file or directory in the ls -1 output.

$ ls -1 | wc -l
6

(note however that it doesn't include the hidden files)

Counting files or directories, just not together

To count either files or directories you need to change your tactic slightly. In this case I'd use ls -l since it shows what's a directory and what's a a file.

Example

$ ls -l
total 12
drwxrwxr-x 2 saml saml 4096 Nov 16 09:48 dir1
drwxrwxr-x 2 saml saml 4096 Nov 16 09:48 dir2
drwxrwxr-x 2 saml saml 4096 Nov 16 09:48 dir3
-rw-rw-r-- 1 saml saml    0 Nov 16 09:49 fileA
-rw-rw-r-- 1 saml saml    0 Nov 16 09:49 fileB
-rw-rw-r-- 1 saml saml    0 Nov 16 09:49 fileC

Then we can use grep to filter out directories or not-directories like so:

# directories
$ ls -l | grep "^d"
drwxrwxr-x 2 saml saml 4096 Nov 16 09:48 dir1
drwxrwxr-x 2 saml saml 4096 Nov 16 09:48 dir2
drwxrwxr-x 2 saml saml 4096 Nov 16 09:48 dir3

# regular files
$ ls -l | grep "^-"
-rw-rw-r-- 1 saml saml    0 Nov 16 09:49 fileA
-rw-rw-r-- 1 saml saml    0 Nov 16 09:49 fileB
-rw-rw-r-- 1 saml saml    0 Nov 16 09:49 fileC

Now just use wc -l again to count the above:

# directories
$ ls -l | grep "^d" | wc -l
3

# regular files
$ ls -l | grep "^-" | wc -l
3

Though, you can avoid wc altogether, and use grep's -c option:

$ ls -l | grep -c '^d'

(again, hidden files are not included. Note that directories and regular are two types of files. There are many more like named pipes, symbolic links, devices, sockets...).

Recursion

If you need to find the files and directories recursively under /usr/bin then you'll likely want to change tactics entirely and make use of another tool called find.

Example

$ find /usr/bin | wc -l
4632

(though above /usr/bin itself is included in the count)

The same techniques I used above could be employed use ls to do something similar but ls is generally not a good tool to parse the output. find on the other hand was built for this, and offers switches to find either files or directories.

# find files
$ find /usr/bin -type f

# find directories
$ find /usr/bin -type d

(note that this time, find is including hidden files (except . and ..)).

newlines?

I've never figured out why a newline character is a legal character to use when creating file names or directory names. So the methods discussed above using wc and ls would not contend with these, so use them with that in mind.

Example

Create a directory & file name with newlines.

$ mkdir $'dir4\n5'
$ touch $'fileD\nE'

ls shows them correctly:

$ ls -1
dir1
dir2
dir3
dir4?5
fileA
fileB
fileC
fileD?E

But wc counts the directories and files that contain newlines as 2 items, not one.

$ ls -1 | wc -l
10

One method to get around this, if using the GNU implementation of find is to make use of find's ability to print something else in place of each file that it finds and then count those instead.

Example

$ find . -printf . | wc -c
9

Here we're finding everything in the current directory (except ..), and printing a dot (.) for each, and then counting the dots using wc's ability to count bytes instead of lines, wc -c.

References

  • bash - What is the best way to count find-results?

If you want to get a break down of the number of each type of file recursively under some dir, with GNU find, you could do:

find /some/dir/. ! -name . -printf '%y\n' | sort | uniq -c | sed '
  s/f/regular files/;t
  s/d/directories/;t
  s/l/symbolic links/;t
  s/s/Unix domain sockets/;t
  s/b/block devices/;t
  s/c/character devices/;t
  s/p/FIFOs/;t
  s/D/Doors/;t
  s/n/network special files/;t
  s/.$/others (&)/'

On /usr/bin on my system, that gives:

   3727 regular files
    710 symbolic links

On /dev:

     83 block devices
    203 character devices
     31 directories
    426 symbolic links
      1 FIFOs
      1 Unix domain sockets

For symlinks, if you'd rather count them as the type of the file they point to rather than symbolic links, you can change it to:

find /some/dir/. ! -name . -printf '%Y\n' | sort | uniq -c | sed '
  s/f/regular files/;t
  s/d/directories/;t
  s/N/broken symbolic links/;t
  s/s/Unix domain sockets/;t
  s/b/block devices/;t
  s/c/character devices/;t
  s/p/FIFOs/;t
  s/D/Doors/;t
  s/n/network special files/;t
  s/.$/others (&)/'

Which now gives for my /usr/bin:

      1 directories
   4434 regular files
      2 broken symbolic links

(a broken symlink is a symlink to a file for which find cannot determine the type either because the file doesn't exist, or is in a directory you don't have access to or there's a loop in the resolution of the path of the file. In my case, those 2 where symlinks to files that are now gone).

None of those count . and ... If you wanted them included (why would you?), there's no other way with find than assume they're there for every directory and count them systematically:

find /some/dir/. -printf '%y\n' \( -name . -printf 'd\n' -o \
  -type d -printf 'd\nd\n' \)  | sort | uniq -c | sed '
  s/f/regular files/;t
  s/d/directories/;t
  s/l/symbolic links/;t
  s/s/Unix domain sockets/;t
  s/b/block devices/;t
  s/c/character devices/;t
  s/p/FIFOs/;t
  s/D/Doors/;t
  s/n/network special files/;t
  s/.$/others (&)/'

Which then gives on my /usr/bin:

      2 directories
   3727 regular files
    710 symbolic links

If you don't have access to the GNU find, you can rewrite the first one as:

find /some/dir/. ! -name . \( \
  -type f -exec printf '%.0sregular files\n' {} + -o \
  -type d -exec printf '%.0sdirectories\n' {} + -o \
  -type l -exec printf '%.0ssymbolic links\n' {} + -o \
  -type s -exec printf '%.0sUnix domain sockets\n' {} + -o \
  -type b -exec printf '%.0sblock devices\n' {} + -o \
  -type c -exec printf '%.0scharacter devices\n' {} + -o \
  -type p -exec printf '%.0sFIFOs\n' {} + -o \
  -exec printf '%.0sothers\n' {} + \) | sort | uniq -c

Now, strictly speaking, we've not been counting files but directory entries. A directory like /usr/bin typically has several entries that point to the same file. For instance, here, I have:

$ ls -lid /usr/bin/{nvi,nview,nex}
672252 -rwxr-xr-x 3 root root 434616 May 25 07:40 /usr/bin/nex
672252 -rwxr-xr-x 3 root root 434616 May 25 07:40 /usr/bin/nvi
672252 -rwxr-xr-x 3 root root 434616 May 25 07:40 /usr/bin/nview

Those are 3 directory entries (aka file names aka hard links) to the same file (the one with inode 672252. To count files instead of directory entries and with GNU find and GNU uniq (ignoring . and .. files which anyway are hard links to other directories):

find /some/dir/. ! -name . -printf '%y\t%D:%i\n' |
  sort -u |
  cut -f1 |
  uniq -c |
  sed '
    s/f/regular files/;t
    s/d/directories/;t
    s/l/symbolic links/;t
    s/s/Unix domain sockets/;t
    s/b/block devices/;t
    s/c/character devices/;t
    s/p/FIFOs/;t
    s/d/Doors/;t
    s/n/network special files/;t
    s/.$/others (&)/'

On my /usr/bin, that gives:

   3711 regular files
    710 symbolic links