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!

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
  • 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. – yukashima huksay Jan 11 '18 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 '18 at 6:53
  • I tried the last code. It only works fine if the directories are empty! – yukashima huksay Jan 11 '18 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 '18 at 7:52
  • @yukashimahuksay OK. I just updated the answer. Try it and we'll talk tomorrow. – John1024 Jan 11 '18 at 8:06

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.