1

I need a script that counts the files in a directory (and sub). I've taken the followed script, and changed it to my need.

It works like it should, except for folders with space characters. I'm pretty sure I'm missing quotes anywhere, but can't figure it out yet.

Additional Informations

  • Linux 2.6.22.19-0.4-default (this server is not in a productive environment anymore.)
  • GNU find version 4.2.31
  • I'm not able to rename the directories.

Example Directory Structure

.
..
01_infos
02_sent
03_inbox
04_public and private
197.
145.
329.
13.

Script

#!/bin/bash
# Write a script that will count the number of files in each of your subdirectories.
# -------------------------------------------------------------------------
# Copyright (c) 2001 nixCraft project <http://cyberciti.biz/fb/>
# This script is licensed under GNU GPL version 2.0 or above
# -------------------------------------------------------------------------
# This script is part of nixCraft shell script collection (NSSC)
# Visit http://bash.cyberciti.biz/ for more information.
# -------------------------------------------------------------------------

START=$HOME

# change your directory to command line if passed
# otherwise use home directory
[ $# -eq 1 ] && START=$1 || :

if [ ! -d $START ]
then
        echo "$START not a directory!"
        exit 1
fi

# use find command to get all subdirs name in DIRS variable
DIRS=$(find "$START" -type d)

# loop thought each dir to get the number of files in each of subdir
for d in $DIRS
do
        echo "$d directory has $(find "$d" -maxdepth 1 -regex '.*\.' -type f | wc -l) files" || :
done

output

./01_infos directory has 1 files
./02_sent directory has 9 files
./03_inbox has 4 files
find: ./04_public: No such file or directory

3 Answers 3

2

You're missing some double quotes (always put double quotes around variable substitutions $foo and command substitutions $(foo), unless you know why you can safely leave them off and need to leave them off). But that's not the whole of the problem.

if [ ! -d $START ]

should be if [ ! -d "$START" ].

DIRS=$(find "$START" -type d)

At this point, DIRS contains name of the start directory and its subdirectories recursively, with newlines in between. So if you have any directory name that contains a newline, you've lost: it's impossible to know which newlines came from a directory name and which were separators. If you know that there are no newlines in file names, you can parse the output of find, but how would you know?

By the way, it's ok to not have double quotes around $(…) here because this is a variable assignments, and substitutions in assignments are implicitly protected. However, note that export DIRS=$(…) is not similarly protected. Best to use quotes unless you're fluent in shell scripting and so are all the people who will maintain your script.

for d in $DIRS

This is where you lose: you want to split $DIRS into words, so you can't put double quotes, but you need double quotes because $DIRS has all the elements concatenated together and the file names inside spaces would be separators if you leave them unquoted.


Usually, when you use find, you should make it invoke the processing command, with the -exec option. Unless you have tight controls on the file names, don't parse the output of find: it's ambiguous.

find "$START" -type d -exec sh -c '
    echo "$0 directory has $(find "$0" -maxdepth 1 -regex ".*\\." -type f -printf \\n | wc -l) files whose name ends with ."
' {} \;

Note again in the embedded find command that if you parse the output of find, your count will be off if any file name contains a newline.

6
  • The last command will not work though, since you forgot to change $d to {} and then it still it does not work for counting newlines. find receives them properly, but it looks like its regex engines don't like it and the newline breaks the matching. Using -path "*\\." makes it work. Jul 4, 2012 at 6:58
  • @lynxlynxlynx That was a copy-pasto, thanks, I forgot to change $d into $0 (not {}! That would be non-portable and impossible to quote properly). I kept the regexp as it was in the question. Hmmm, so this particular regexp does exclude file names containing newlines, meaning that -print | wc -l would actually work in the inner find. Jul 4, 2012 at 7:38
  • Doesn't work for me, while {} does with no extra quoting. For portability, I see no other way than to split it up with xargs. Jul 4, 2012 at 7:48
  • @lynxlynxlynx Make sure you copied properly. I tested it, and the counts came out right. What error do you observe? Jul 4, 2012 at 8:16
  • it returns a 0 count for my newlined folder. Jul 4, 2012 at 8:24
1

How about just this?

find . -type d -exec sh -c '/bin/echo -n "{}"; find "{}" -maxdepth 1 -regex ".*\." -type f | wc -l; ' \;

The output is not as sugary, but it doesn't require a script and it works for directories with spaces, as well as other non-alphanumeric characters.

1
  • except for newlines, see below. Jul 4, 2012 at 8:04
-2

You have a classic quoting mistake. Fix the for loop to look like this:

for d in "$DIRS"

Alternatively you could feed it find's output directly, eg.:

find "$START" -type d | while read d
do # and so on...

As an aside, the || : bit is completely redundant, since echo always has a return value of 0.

3
  • 1
    "$DIRS" is no good: the for loop body will run only once, on the concatenation of all the directory names. while read d should be while IFS= read -r d, and that will still choke on directory names containing newlines. Jul 4, 2012 at 0:29
  • nice catch and seems that read can't take -d$'\0'. Jul 4, 2012 at 6:38
  • Most shells can't handle null characters. Zsh can, and has a number of other advantages besides, but it's rarely installed by default. Jul 4, 2012 at 7:39

You must log in to answer this question.

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