If I do ls -1 target_dir | wc -l, I get a count of files in a directory. I find this a bit cumbersome. Is there a more elegant or succinct way?

  • 2
    You don't need the "-1" when piping to wc.
    – Steve
    May 1, 2014 at 18:18
  • ls already gives the total count, so how about ls -l | head -1? Make it an alias if you want something shorter. May 2, 2014 at 4:19
  • 2
    @DanielWagner The "total: nnn" output by ls -l indicates the total size of the files, not the number of files. May 2, 2014 at 7:58
  • 2
    Keep in mind that ls | wc -l will give you the wrong count if any file names contain newlines.
    – chepner
    May 2, 2014 at 19:29
  • This depends of file-system, and counts directories + 2 in a directory. The answer has 2 extra ( as it counts itself, and its parent). stat -c %h . gives the same information as ls -ld . | cut -d" " -f 2 Aug 2, 2015 at 22:50

5 Answers 5


Assuming bash 4+ (which any supported version of Ubuntu has):

num_files() (
    shopt -s nullglob
    cd -P -- "${1-.}" || return
    set -- *
    echo "$#"

Call it as num_files [dir]. dir is optional, otherwise it uses the current directory. Your original version does not count hidden files, so neither does this. If you want that, shopt -s dotglob before set -- *.

Your original example counts not only regular files, but also directories and other devices -- if you really only want regular files (including symlinks to regular files), you will need to check them:

num_files() (
    local count=0

    shopt -s nullglob
    cd -P -- "${1-.}" || return
    for file in *; do
        [[ -f $file ]] && let count++
    echo "$count"

If you have GNU find, something like this is also an option (note that this includes hidden files, which your original command did not do):

num_files() {
    find "${1-.}" -maxdepth 1 -type f -printf x | wc -c

(change -type to -xtype if you also want to count symlinks to regular files).

  • Won't set fail if there are very many files? I think you might have to use xargs and some summing code to make that work in the general case.
    – l0b0
    May 1, 2014 at 11:31
  • 1
    Also shopt -s dotglob if you want files starting with . to be counted May 1, 2014 at 15:19
  • 1
    @l0b0 I don't think set will fail under these circumstances, since we're not actually doing an exec. To wit, on my system, getconf ARG_MAX yields 262144, but if I do test_arg_max() { set -- {1..262145}; echo $#; }; test_arg_max, it happily replies 262145.
    – kojiro
    May 1, 2014 at 17:52
  • @DavidRicherby -maxdepth is not POSIX.
    – Chris Down
    May 1, 2014 at 21:27
  • 4
    @MichaelMartinez Writing obvious code is not a substitute for writing correct code.
    – Chris Down
    May 2, 2014 at 5:51

f=(target_dir/*);echo ${#f[*]}

works correctly for file with spaces, newlines, etc. in the name.

  • can you provide some context? Should this go in a bash script?
    – codecowboy
    May 2, 2014 at 7:47
  • it could. you could also put it directly in the shell. that version assumed you wanted the current directory; i've edited it so it's closer to your question. basically it creates a shell array variable containing all the files in the directory, then prints the count of that array. should work in any shell with arrays -- bash, ksh, zsh, etc. -- but probably not plain sh/ash/dash. May 2, 2014 at 15:40

ls is multi-columns only if it outputs directly to a terminal, you can remove the "-1" option, You can remove the wc "-l" option, only read the first value (lazy solution, not to be used for legual evidences, criminal investigations, mission critical, tactical ops..).

ls target | wc 
  • 5
    This fail for filenames containing newlines.
    – l0b0
    May 1, 2014 at 11:29
  • @Emmanuel You'll need to parse the result of your wc to get the number of files in even the trivial case, so how is this even a solution?
    – l0b0
    May 2, 2014 at 7:22
  • @Emmanuel This can fail if target is a glob which, when expanded, includes some things that start with hyphens. For example, make a new directory, go into it and do touch -- {1,2,3,-a}.txt && ls *|wc (NB: use rm -- *.txt to delete those files.) May 2, 2014 at 7:51
  • Did you mean wc -l? Otherwise you get newline, word, and byte counts of the ls output. That is what David Richerby said: You have to parse it again.
    – erik
    May 2, 2014 at 20:55
  • @erik I ment wc with no argument you don't need to parse if your brain knows that the first argument is newline.
    – Emmanuel
    May 2, 2014 at 22:06

If it's succinctness you're after (rather than exact correctness when dealing with files with newlines in their names, etc.), I recommend just aliasing wc -l to lc ("line count"):

$ alias lc='wc -l'
$ ls target_dir|lc

As others have noted, you don't need the -1 option to ls, since it's automatic when ls is writing to a pipe. (Unless you have ls aliased to always use column mode. I've seen that before, but not very often.)

An lc alias is quite handy in general, and for this question, if you look at the "count the current directory" case, ls|lc is about as succinct as you can get.


So far Aaron's is the only approach more succinct than your own. A more correct version of your approach might look like:

ls -aR1q | grep -Ecv '^\./|/$|^$'

That recursively lists all files - not directories - one per line including .dotfiles beneath the current directory using shell globs as necessary to replace non-printable characters. grep filters out any parent directory listings or .. or */ or blank lines - so there should only be one line per file - the total count of which grep returns to you. If you want child directories included as well do:

ls -aR1q | grep -Ecv '^\.{1,2}/|^$'

Remove the -R in either case if you do not want recursive results.

  • 1
    I tend to prefer to do that kind of thing with find. If you only want a count, this should work: find -mindepth 1 -maxdepth 1 -printf '\n'|wc -l (remove the depth controls to get recursive results). Aug 17, 2015 at 21:33
  • @AaronDavies - that doesn't actually work. Put a newline in any of those filenames and see for yourself. Also, to do the same thing portably you do: find . \! -name . -prune | wc -l - which still doesn't work, of course.
    – mikeserv
    Aug 19, 2015 at 4:45
  • 1
    I don't follow -- the printf instruction prints a constant string (a newline) that doesn't include the filename at all, so the results are independent of any strange filenames. This trick can't be done at all with a find which doesn't support printf, of course. Aug 29, 2015 at 23:37
  • @AaronDavies - oh, true. I assumed the filename was included. It can be done portably, though, of course: find .//. \!. -name . -prune | grep -c '^\.//\.'
    – mikeserv
    Sep 8, 2015 at 7:16
  • brilliant! / being the only other character that can't appear in filenames, the .//. sequence is guaranteed to appear exactly once for every file, right? a couple questions though -- why .//., and why -prune? when would this differ from find . \! -name . | grep -c '^\.'? (i assume the . in your \!. is a typo.) Sep 18, 2015 at 3:45

You must log in to answer this question.

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