8

How can I use word counter (wc) and piping to count how many files or directories are in the /usr/bin directory?

  • This homework?? It's OK to ask for help, just identify it as such, if it is. – slm Nov 16 '13 at 14:47
  • yes it is but I post here to get an idea on how to achieve something since I'm new to Linux and it can be very complicated. And I already solve the question above with this command – cash Nov 16 '13 at 15:43
  • ls /bin /usr/bin | sort | uniq | wc - – cash Nov 16 '13 at 15:44
  • np. It's perfectly fine to ask for help! Just label it so people know, everyone here is usually happy to help people that are trying to learn the finer points of Unix. – slm Nov 16 '13 at 17:30
11

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

  • While files in /usr/bin are all going to be well-formatted (and also won't contain spaces, so technically you could even just echo * | wc -w), it's worth noting that all of these will break on filenames containing newlines. – evilsoup Nov 16 '13 at 15:07
  • @evilsoup - no I don't believe the ls -l or ls -1 will break b/c we're counting lines, not words! The find may break, but again, we're counting lines not words. – slm Nov 16 '13 at 15:21
  • What I mean is that this will (I think, I'm on Windows right now so I can't test) break if the files contain newlines. So touch $'foo\nbar' within an empty directory followed by one of your commands (say ls -1 | wc -l) will report two files rather than one -- because that one file is two lines as far as wc is concerned. Unless ls replaces newlines with some other character (I don't think it does, but again I'm not in a position to test right now). – evilsoup Nov 16 '13 at 15:33
  • @evilsoup - correct, newline char. is a legal char. for file names, and the methods would not be able to contend with those types of filenames correctly. – slm Nov 16 '13 at 17:54
  • @StephaneChazelas - is wc -c an issue when counting the periods? – slm Nov 17 '13 at 16:19
5

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
0

You haven't said if you want all the file under /usr/bin recursively or just under the first level. Also, how are you going to get the words that you are counting? The usual way to find out is to run find into wc. Like this: find /usr/bin | wc -l Find will list everything there, directories & files. Wc -l will count all the lines in the find output. Is this a class assignment? It's ok if it is but I was wondering why you needed this info so that I could tailor the response more carefully. Please let me know if you need more. Costa

0

In bash, without external tools.

cd dir/ || exit; shopt -s nullglob; shopt -s dotglob; count=(*); echo "${#count}"

In bash, without external tools and recursion.

shopt -s globstar; shopt -s dotglob 
for dir in **/*/; do 
  unset d f
  for files in "$dir"*; do 
    [[ -f $files ]] && ((++f))
    [[ -d $files ]] && ((++d))
  done; 
  printf '%s\n' "$dir -  files: ${f:-0} - directories: ${d:-0}"
done
  • Note that the second would follow symlinks when recursing (and count symlinks to regular files as regular files, and symlinks to dirs as dirs), would not count files and directories in the current directory and would not count . nor .. entries. You may want to disambiguate file vs regular file. – Stéphane Chazelas Nov 16 '13 at 20:49

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.