3

I want to make a bash function that would behave just like how wc -l behaves on multiple files to count their number of lines in counting the number of files in a set of directories. How wc -l works:

wc -l test.zip  tt.zip zzz.zip | sort
     17 tt.zip
   2015 test.zip
   6567 zzz.zip
   8599 total

How I want my function to work on files:

count dir1 dir2 dir3 | sort:
      1 dir1
    144 dir2
   1000 dir3
   1145 total

Where dir{1..3} are 3 directories and the number of files shown includes the hidden files.

What I've already done:

#/bin/bash
count() {
    if [ "`file -b $1`" == 'directory' ] ; then    
        echo `la "$1" | wc -l`
    else
        wc -l "$@" | sort
    fi
}

I can implement it with a for loop on $@ but I'd rather find an easier solution. If you can also help me include the size of each directory. You would make me really happy!

1 Answer 1

2

Try:

find dir1 dir2 dir3 -maxdepth 1 -type f -printf '%h\n' | awk '{c[$0]++} END{for (dir in c) printf "%6i %s\n",c[dir],dir}' | sort -n

If the directories are specified in $@, then use:

find "$@" -maxdepth 1 -type f -printf '%h\n' | awk '{c[$0]++} END{for (dir in c) printf "%6i %s\n",c[dir],dir}' | sort -n

How it works

  1. find dir1 dir2 dir3 -maxdepth 1 -type f -printf '%h\n'

    This looks for all regular files in directories dir1, dir2, and dir3. For each file found, its directory is printed.

    -maxdepth 1 (optional) tells find not to dive into subdirectories. -type f tells find to only report on regular files. For each file found, -printf '%h\n' tells find to print the directory that the file is in.

  2. awk '{c[$0]++} END{for (dir in c) printf "%6i %s\n",c[dir],dir}'

    This counts the number of times each directory appears on the input. After all the input has been read, it prints the totals.

    We use associative array c to count the number of times each directory is seen. In awk, $0 is the contents of the current line being read. c[$0] is the number of times that line has been seen so far. c[$0]++ increments that count by one.

  3. sort -n

    This sorts the output in ascending order of file count. (-n tells sort to sort numerically rather than alphabetically.)

Example

Let's suppose that we have these directories with these files:

$ ls dir{1..3}/*
dir1/a.txt  dir1/c.txt  dir1/e.txt      dir1/f.txt  dir2/b.txt  dir2/d.txt  dir2/f.txt  dir3/b.txt
dir1/b.txt  dir1/d.txt  dir1/file3.txt  dir2/a.txt  dir2/c.txt  dir2/e.txt  dir3/a.txt

Our command produces the output:

$ find dir1 dir2 dir3 -maxdepth 1 -type f -printf '%h\n' | awk '{c[$0]++} END{for (dir in c) printf "%6i %s\n",c[dir],dir}' | sort -n
     2 dir3
     6 dir2
     7 dir1

Improvement: Adding a total row

$ find dir1 dir2 dir3 -maxdepth 1 -type f -printf '%h\n' | awk '{c[$0]++} END{for (dir in c) {printf "%6i %s\n",c[dir],dir;tot+=c[dir]}; printf "%6i TOTAL",tot }' | sort -n
     2 dir3
     6 dir2
     7 dir1
    15 TOTAL

To suppress the printing of TOTAL if there is only one directory in the output:

find "$@" -maxdepth 1 -type f -printf '%h\n' | awk '{c[$0]++} END{for (dir in c) {printf "%6i %s\n",c[dir],dir;tot+=c[dir]}; if (length(c)>1)printf "%6i TOTAL",tot }' | sort -n

Include empty directories in the output

To also include empty directories:

find "$@" -maxdepth 1 -type f -printf '%h\n' | awk 'FNR==NR{c[$0]=0; next} {c[$0]++} END{for (dir in c) {printf "%6i %s\n",c[dir],dir;tot+=c[dir]}; if (length(c)>1)printf "%6i TOTAL",tot }' <(printf "%s\n" "$@") <(cat) | sort -n

As an example, let's consider an empty directory:

$ ls dir4

And, let's set $@:

$ set -- dir4

Now, let's run our code:

$ find "$@" -maxdepth 1 -type f -printf '%h\n' | awk 'FNR==NR{c[$0]=0; next} {c[$0]++} END{for (dir in c) {printf "%6i %s\n",c[dir],dir;tot+=c[dir]}; if (length(c)>1)printf "%6i TOTAL",tot }' <(printf "%s\n" "$@") <(cat) | sort -n
     0 dir4

Let's try again with two directories:

$ set --  dir1 dir4
$ find "$@" -maxdepth 1 -type f -printf '%h\n' | awk 'FNR==NR{c[$0]=0; next} {c[$0]++} END{for (dir in c) {printf "%6i %s\n",c[dir],dir;tot+=c[dir]}; if (length(c)>1)printf "%6i TOTAL",tot }' <(printf "%s\n" "$@") <(cat) | sort -n
     0 dir4
     7 dir1
     7 TOTAL
9
  • Thanks. It works very well. I think it's better if you edit your answer to use "$@" to match the context of the question. There are also a few tiny issue. 1- if you ask wc -l to count the number of lines in an empty directory. it will output 0 but your code doesn't output anything for empty directories which can be a little confusing. 2- wc -l won't show total row if you are asking only one file. Which is pretty reasonable. but your code includes the total raw anyway. Jan 11, 2018 at 5:15
  • @yukashimahuksay I have updated the answer to include examples using `$@``. I have also added code that suppresses total when the output has only one directory. Lastly, code is provided to list empty directories has having zero files.
    – John1024
    Jan 11, 2018 at 6:53
  • I tried the last code. It only works fine if the directories are empty! Jan 11, 2018 at 7:31
  • @yukashimahuksay I just checked it and you are absolutely right! Since it's late here now, I hope you don't mind if I fix it tomorrow.
    – John1024
    Jan 11, 2018 at 7:52
  • @yukashimahuksay OK. I just updated the answer. Try it and we'll talk tomorrow.
    – John1024
    Jan 11, 2018 at 8:06

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .