I have lots and lots of files in a series of directories, and would like a nicely formatted list of how many files are in each directory.

The directories are ordered thus: Parent directories named A, B, ... H, I. Under each of these, there are from 1 to 7 directories named 001, 002, ... 007. In these directories, the files I'd like a count of files.

What I'm hoping for, is something like this:

C/003    122
C/004     45
C/005    462
D/001    215
E/001     98
E/002    323

I'm thinking a tab between directory-name and count. It doesn't matter if the count is left or right justified.

What I've done myself is basically this:

( for i in */*
  echo ">>> $i"
  ls "$i" | wc
done ) > file_count

The result is something like this:

>>> F/002
  30   30   234
>>> F/003
  120  120  1322
>>> G/001
  78   78   620

Obviously, not bad... and if I replaced wc with wc -l and removed the ">>>" it would be even better - except that each "record" would be over two lines rather than one.

So what I need, is some awk or sed (or whatever - is there a simpler tool for joining two lines than those two?) magic to join the two-and-two lines into one.

However, if there is a more direct approach (shell-script, perl, python, some really dark sed & awk magic) that could make a list in "one go", that would be even better...


Assuming your file names don't contain newline characters and your grep supports the -o option:

find [[:upper:]] -type f | grep -Eo '^./[0-9]{3}' | sort | uniq -c
  • 1
    FYI this guy discovered the shellshock bug in bash – Punit Arya Aug 28 '15 at 20:17

You can built an array of files for each directory separately, and then just count the number of elements. In bash that would be something like

for dir in */*/; do a=( "$dir"/* ); printf "%s\t%s\n" "$dir:" "${#a[@]}"; done

If A/001 etc. contains directories too which content you would like to include, then add ** glob:

shopt -s globstar
for dir in */*/; do a=( "$dir"/**/* ); printf "%s\t%s\n" "$dir:" "${#a[@]}"; done

The simplest way to get what you want is to use the -n option to echo when you print the directory name. That avoids printing a newline at the end of whatever you're echoing, so the next output stays on the same line. Other languages may be a better option if you want to collect the information you're looking for and then run multiple transformations on it before giving output.


This will do what you've asked:

for d in */*
    n=$(find "$d" -maxdepth 1 -type f -printf "ok\n" | wc -l)
    printf "%s\t%d\n" "$d" $n

The primary differences to your code are that I've used find instead of ls so that weirdly named files won't break the count (think of \n in a filename), and that I've used printf to format the output.


Perhaps something like:

find $PWD/ -type f -printf "%h\n\0" | uniq -zc

Yield something like:

  7 /foo
 17 /foo/bar
  9 /foo/baz

Add a | sort -z before | uniq to sort it.

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.