Take the 2-minute tour ×
Unix & Linux Stack Exchange is a question and answer site for users of Linux, FreeBSD and other Un*x-like operating systems.. It's 100% free, no registration required.

I have a directory, containing a lot of files and directories.

I am trying to get the number of files (and directories) contained recursively in every directory.

I tried the following approach:

for dir in $(find -maxdepth 1 -type d); do echo "$dir"; echo find "$dir" | wc -l; done

But this returns "1" as result for every directory.

I know there are several other questions with a similar question, but I would really like to know what my mistake is in the code above.

share|improve this question

4 Answers 4

up vote 4 down vote accepted

A GNU (bash, wc and find) solution which works with any path, even those containing spaces, newlines or starting with a dash:

shopt -s nullglob
for dir in ./*/
do
    printf '%s\n' "$dir"
    find "$dir" -mindepth 1 -printf x | wc --chars
done

Explanation:

  • The nullglob option prevents errors if ./ contains no directories.
  • The ./ in the directory glob ensures that file names starting with a dash ("-") won't mess up echo or find.
  • The slash at the end of the glob ensures that only directories are processed.
  • -mindepth 1 avoids counting the directory itself.
  • If you want to include directories which start with a dot on the top level, you should run shopt -s dotglob before the for loop.
share|improve this answer
    
Works like a charm. Thank you for your answer and your explanations. –  Majiy Apr 3 '13 at 9:58
2  
+1 for the -printf x : otherwise, the default will output the name of the file (which could : contain "newline", and therefore be counted twice. Or contain other things such as "NULL" characters, making lots of commands choke) –  Olivier Dulac Apr 3 '13 at 11:35
1  
@OlivierDulac A path can not contain null characters. It's in fact the only character they can't contain. –  l0b0 Apr 3 '13 at 11:41
    
@l0b0: good point (I should have known, as I use "-print0" and same with xargs, when available). But still, nice to avoid any "weird" characters out there ^^ (newline, at least) –  Olivier Dulac Apr 3 '13 at 11:49

Here's another method with bash4+. Note that it follows symlinks, and doesn't include . and .. unlike l0b0's answer (which may or may not be what you want):

(
    shopt -s dotglob globstar nullglob
    for dir in */; do
        set -- "$dir"/**/*
        printf '%s: %d\n' "$dir" "$#"
    done
)
share|improve this answer
    
I don't use "/**" (not enough portability) : but does it count the file starting with "." ? (ex: .bashrc, etc) –  Olivier Dulac Apr 3 '13 at 11:37
    
@OlivierDulac - That's dotglob, which is enabled at the top. –  Chris Down Apr 3 '13 at 11:47
    
well, I never use it (not available right now for me) ^^ but I wonder if "/**" would match all but "/**/*" specifically restrict it to non-"." subdirs? Please correct me if i'm wrong –  Olivier Dulac Apr 3 '13 at 11:52
1  
@OlivierDulac - Like I said, dotglob controls that. With dotglob enabled, it matches them. –  Chris Down Apr 3 '13 at 11:57

$(find -maxdepth 1 -type d) outputs the list of directories in the current directory. Unless there are directories whose name begins with a ., this is a complex way of writing */. It's also unreliable: it only works if none of the directory names contain whitespace or globbing characters \[?*. That's because the result of the command substitution $(…) is split into separate words wherever there's a whitespace character, and each word is interpreted as a glob (filename wildcard pattern). You can avoid this behavior by putting the command substitution in double quotes ("$()"), but then the list that the loop iterates on will contain a single element which is the concatenation of the directory names separated by newlines.

Note this shell programming rule: always put double quotes around variable substitutions and command substitutions ("$foo", "$(foo)") unless you know that you need to leave the double quotes out and you understand how it's safe to leave them out.

The other problem with your script is a simple one: echo find "$dir" always prints out one line; you meant find "$dir".

for dir in */; do
  echo "$dir"
  find "$dir" | wc -l
done

Note that this only works if no file inside that tree contains newlines. If they might, you can make the find command print something reliable. With GNU find (i.e. on non-embedded Linux or Cygwin):

for dir in */; do
  echo "$dir"
  find "$dir" -printf a | wc -c
done

Portably:

for dir in */; do
  echo "$dir"
  find "$dir" -exec printf %c {} + | wc -c
done
share|improve this answer

A small, slightly faster variant of Gilles' portable solutions would be:

for dir in */; do
  echo "$dir"
  #find "$dir" -exec printf %c {} + | wc -c
  find "$dir" -print0 | tr -dc '\0' | wc -c
done
share|improve this answer

Your Answer

 
discard

By posting your answer, you agree to the privacy policy and terms of service.

Not the answer you're looking for? Browse other questions tagged or ask your own question.