18

I'm learning bash scripting and have written a script to count the files and directories in the directory that is supplied as argument. I have it working one way which seems odd to me and am wondering if there is a simpler way of doing it.

I have commented out the code that will work, but left it in as a comparison. I am trying to get the for-loop working, instead using if statements inside it to detect if an item in the given location is a file or a directory.

Edit: I just found out that the commented code counts all files and directories in the subdirectories of the given location as well! Is there any way to prevent this and just count the files and directories of the given location?

#!/bin/bash

LOCATION=$1
FILECOUNT=0
DIRCOUNT=0

if [ "$#" -lt "1" ]
then
    echo "Usage: ./test2.sh <directory>"
    exit 0
fi

#DIRS=$(find $LOCATION -type d)
#FILES=$(find $LOCATION -type f)

#for d in $DIRS
#do
#   DIRCOUNT=$[$DIRCOUNT+1]
#done

#for f in $FILES
#do
#   FILECOUNT=$[$FILECOUNT+1]
#done

for item in $LOCATION
do
if [ -f "$item" ]
    then
         FILECOUNT=$[$FILECOUNT+1]
    elif [ -d "$item" ]
        then
         DIRCOUNT=$[$DIRCOUNT+1]
fi
done

echo "File count: " $FILECOUNT
echo "Directory count: " $DIRCOUNT

For some reason the output of the for-loop, no matter where I point the location to, always returns:

File count: 0 , Directory count: 1
Rubens
  • 14,478
  • 11
  • 63
  • 92
Alan Smith
  • 1,069
  • 4
  • 19
  • 23
  • Your whole script is broken if there happens to be spaces in file names. As you're learming, my first advice is: Use More Quotes! – gniourf_gniourf Dec 05 '12 at 15:57
  • Another comment: I can tell that your sources for learning bash are outdated and don't show good practices. – gniourf_gniourf Dec 05 '12 at 15:58
  • On Filename Expansion consider reading: http://bash.cyberciti.biz/bash-reference-manual/Filename-Expansion.html – hovanessyan Dec 05 '12 at 16:00
  • Hi, I don't understand this, where are there spaces in file names? – Alan Smith Dec 05 '12 at 16:08
  • You could have a space in a filename. Try this: `touch "hello Kitty"`, you'll have a file with a space in its name. Worse, try `touch $'hello\nKitty'`, you'll have a file the name of which will not be easy to handle with the methods involving `find`. – gniourf_gniourf Dec 05 '12 at 16:20
  • my `find` command handles files with spaces and newlines. – dogbane Dec 05 '12 at 16:22
  • If I don't use the find command will this be an issue? ie. if I stick to using: for item in $LOCATION/* $LOCATION/.* – Alan Smith Dec 05 '12 at 16:27

5 Answers5

19

Use find as shown below. This solution will count filenames with spaces, newlines and dotfiles correctly.

FILECOUNT="$(find . -type f -maxdepth 1 -printf x | wc -c)"
DIRCOUNT="$(find . -type d -maxdepth 1 -printf x | wc -c)"

Note that the DIRCOUNT includes the current directory (.). If you do not want this, subtract 1.

((DIRCOUNT--)) # to exclude the current directory
dogbane
  • 266,786
  • 75
  • 396
  • 414
  • This is obviously the best possible method in this particular case! As dogbane mentioned, it handles all possible cases of files having funny symbols in their name, and also works well with directories that contain a lot of files (which is not the case for the bash solutions using globbing). One thing, though, my `find` complains about `-maxdepth 1` being given after the `-type f` option. (Another thing, the double quotes are not necessary here, but it never hurts to use them). – gniourf_gniourf Dec 05 '12 at 16:36
  • Note that the `-printf` primary is not available on macOS. – MaxPowers Feb 17 '23 at 11:06
14

To just solve the problem you can use:

FILECOUNT=$(find $LOCATION -type f | wc -l)
DIRCOUNT=$(find $LOCATION -type d | wc -l)

find will look for all files (-type f) or directories (-type d) recursively under $LOCATION; wc -l will count the number of lines written to stdout in each case.

However if you want to learn, the bash script may be a better way. Some comments:

  • If you want to look for files/directories in $LOCATION only (not recursively under their subdirectories etc), you can use for item in $LOCATION/*, where the * will expand to the list of files/directories in the $LOCATION directory. The missing * is why your original script returns 0/1 (becasue the $LOCATION directory itself is the only item counted).
  • You may want to check first that $LOCATION is actually a directory with [ -d $LOCATION ].
  • For arithmetic expressions, use $(( ... )), for example FILECOUNT=$(( FILECOUNT + 1 )).
  • If you want to find all files/directories recursively, you could combine find with a loop.

Example:

find $LOCATION | while read item; do
    # use $item here...
done
Anders Johansson
  • 3,926
  • 19
  • 19
  • Yes, the $LOCATION/* code worked perfectly. Why $(( ... )) instead of $[] ? Performance related? – Alan Smith Dec 05 '12 at 16:14
  • @AlanSmith, `$[...]` is deprecated syntax, I can't even find it in `man bash`. The modern version is `$(( ... ))` (though they seem to work the same way). For your task you could just use `((++FILECOUNT))` too. See http://stackoverflow.com/questions/2188199/bash-double-or-single-bracket-parentheses-curly-braces. – Anders Johansson Dec 05 '12 at 16:24
5

You're not iterating over the list of files inside the given directory; add /* after $LOCATION. Your script should look like:

...
for item in $LOCATION/*
do
...

As pointed by dogbane, just adding /* will count only files that does not begin with .; for doing so, you shall do the following:

...
for item in $LOCATION/* $LOCATION/.*
do
...
Rubens
  • 14,478
  • 11
  • 63
  • 92
3

... am wondering if there is a simpler way of doing it.

If you say so ;)

Alternatively, reduce your script to

find /path/to/directory | wc -l

For current directory, do:

find . | wc -l
sampson-chen
  • 45,805
  • 12
  • 84
  • 81
  • Note: It counts `.` as a file too. So with only two regular files which I guess you want to count, this will print `3`. – Jite Dec 05 '12 at 16:01
  • He's looking for a way to distinguish the counts of dirs and files - that's why he has 2 counters in his code. Your find gives the total count, not distinguishing between file types. – hovanessyan Dec 05 '12 at 16:02
  • if i am using the command " find . | wc -l " it is giving 1 extra count. Like if the file count in the directory is 4 , for me this command is giving 5 in the place of 4. –  Aug 21 '18 at 13:53
2

Use

find $LOCATION -maxdepth 1 -type f | wc -l

For the count of files, and

find $LOCATION -maxdepth 1 -type d | wc -l

For counting directories

beny23
  • 34,390
  • 5
  • 82
  • 85