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.

If parsing the output of ls is dangerous because it can break on some funky characters (spaces, \n, ... ), what's the best way to know the number of files in a directory?

I usualy rely on find to avoid this parsing, but similarly, find mydir | wc -l will break for the same reasons.

I'm working on Solaris right now, but I'm looking for a answer as portable across different unices and different shells as possible.

share|improve this question

6 Answers 6

up vote 12 down vote accepted

How about this trick?

find . -maxdepth 1 -exec echo \; | wc -l

As portable as find and wc.

share|improve this answer
2  
This doesn't work (it displays n+1 files on my Debian system). It also doesn't filter for regular files. –  Chris Down Dec 23 '11 at 13:35
2  
I just gave a generic example. It does work, but how it works depends on how you adapt the find command to your specific needs. Yes, this one includes all the directories, including . (which might be why you see the result as n+1). –  rozcietrzewiacz Dec 23 '11 at 15:35
    
I like this trick, very clever; but I'm surprised there's no simple straightforward way to do that! –  rahmu Dec 23 '11 at 16:11
3  
@ChrisDown the OP doesn't specify filtering for regular files, asks for number of files in a directory. To get rid of the n+1 issue, use find . -maxdepth 1 ! -name . -exec echo \; | wc -l; some older versions of find do not have -not. –  Arcege Dec 23 '11 at 16:50

With bash, without external utilities, nor loops:

shopt -s dotglob
files=(*)
echo ${#files[@]}

In ksh, replace shopt -s dotglob by FIGNORE=.?(.). In zsh, replace it by setopt glob_dots, or remove the shopt call and use files=(*(D)). (Or just drop the line if you don't want to include dot files.) Portably, if you don't care about dot files:

set -- *
echo $#

If you do want to include dot files:

set -- *
if [ -e "$1" ]; then c=$#; else c=0; fi
set .[!.]*
if [ -e "$1" ]; then c=$((c+$#)); fi
set ..?*
if [ -e "$1" ]; then c=$((c+$#)); fi
echo $c
share|improve this answer
    
+1; this is really nice and dwarfs find in terms of speed ( about 600 times faster when counting over 200K files/subdirs). –  don_crissti Jun 12 '13 at 9:36

Try:

ls -b1A | wc -l

The -b will have non-printable characters, -A will show all files except . and .. and one per line (the default on a pipe, but good to be explicit).

As long as we're including higher-level scripting languages, here's a one-liner in Python:

python -c 'import os; print len(os.listdir(os.sep))'

Or with full 'find':

python -c 'import os; print len([j for i in os.walk(os.sep) for j in i[1]+i[2]])'
share|improve this answer

Yoc can use such construction:

I=0; for i in * ; do ((I++)); done ; echo $I

But I'm afraid, you can cath error like Argument list too long. in case you have too many files in directory. However I tested it on directory with 10 billion files, and it worked well.

share|improve this answer
3  
THis won't work for hidden files either unless the shell is configured to expand those with *. –  Lekensteyn Dec 23 '11 at 11:53
    
gnu find . -maxdepth 1 -type f | wc -l –  Nikhil Mulley Dec 23 '11 at 14:41
3  
@Rush: this command should never raise "arg list too long". That only happens with external command (so never with for. –  enzotib Dec 23 '11 at 16:16

Have you considered perl, which should be relatively portable?

Something like:

use File::Find;

$counter = 0;

sub wanted { 
  -f && ++$counter
}

find(\&wanted, @directories_to_search);
print "$counter\n";
share|improve this answer

Try this => Using ls with -i ( for node number ) & -F (appends directory name with '/' ) options.

ls -ilF | egrep -v '/' | wc -l
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.