6

I'm looking for a script that counts files in the current directory (excluding sub directories). It should iterate through each file and if not a directory increment a count. The output should just be an integer representing the number of files.

I've come up with

find . -type f | wc -l

But I don't really think it does the whole counting bit. This is for an assignment so if you only want point me in the right direction that would be great.

marked as duplicate by G-Man, Stephen Harris, Anthon, Networker, don_crissti Aug 2 '16 at 13:54

This question has been asked before and already has an answer. If those answers do not fully address your question, please ask a new question.

  • 2
    It's very close indeed. All you need is an extra operator to tell find not to descend into subdirectories. The -maxdepth 1 will do that for you. – roaima Aug 1 '16 at 9:46
  • There's been some updates in the answers as to whether "hidden" files should count or not - do they? Also, find-based answers do not explicitly increment a counter -- is that a requirement? – Jeff Schaller Aug 1 '16 at 15:41
  • … although the correct solution doesn’t appear until the last six lines of slm’s answer. Stéphane Chazelas also provides a very inclusive answer. – G-Man Aug 1 '16 at 23:03
9

If you want only regular files,

With GNU find:

find . -maxdepth 1 -type f -printf . | wc -c

Other finds:

find . ! -name . -prune -type f -print | grep -c /

(you don't want -print | wc -l as that wouldn't work if there are file names with newline characters).

With zsh:

files=(*(ND.)); echo $#files

With ls:

ls -Anq | grep -c '^-'

To include symlinks to regular files, change -type f to -xtype f with GNU find, or -exec test -f {} \; with other finds, or . with -. with zsh, or add the -L option to ls. Note however that you may get false negatives in cases where the type of the target of the symlink can't be determined (for instance because it lies in a directory you don't have access to).

If you want any type of file (symlink, directory, pipes, devices...), not only regular one:

find . ! -name . -prune -printf . | wc -c

(change to -print | grep -c / with non-GNU find, (ND.) to (ND) with zsh, grep -c '^-' with wc -l with ls).

That will however not count . or .. (generally, one doesn't really care about those as they are always there) unless you replace -A with -a with ls.

If you want all types of files except directories, Replace -type f with ! -type d (or ! -xtype d to also exclude symlinks to directories), and with zsh, replace . with ^/, and with ls, replace grep -c '^-' with grep -vc '^d'.

If you want to exclude hidden files, add a ! -name '.*' or with zsh, remove the D or with ls, remove the A.

  • You mentioned that grep -c / is not guaranteed to work — so wouldn’t it be better, in your POSIX example, to do -exec echo ";" or -exec dirname -- {} +, and then count lines (with wc -l)?   Granted, the dirname example will crash and burn if the name of the starting directory contains newline(s) (e.g., find $'foo\nbar' …), but ! name . assumes that you’re doing find . …, doesn’t it?   I would suggest -exec stat -c "" -- {} +, but stat isn’t in POSIX either (right?). – G-Man Aug 1 '16 at 23:31
  • @G-Man, that's theoretical. POSIX leaves the behaviour of grep unspecified on non-text input. Here, it could be non-text if filenames contain non-characters (LC_ALL=C would work around that), or if the file paths are larger than LINE_MAX. Here for one level of directory, in practice, that won't happen. Even if that happened, in practice, what I see in implementations that have a LINE_MAX limit for grep is it only looking at the first LINE_MAX bytes of the line which is OK here as the / is in 2nd position. That / would also appear before the first invalid character if any. – Stéphane Chazelas Aug 2 '16 at 9:16
  • @G-Man, stat is the archetype of the non-portable command. And among the numerous incompatible implementations, the GNU one (which you seem to be refering to with that -c syntax) is one of the worst IMO (especially considering that GNU find had a much better interface (with the -printfsyntax different from GNU stat's (!?)) long before GNU stat was created. Here you could do find . -exec printf '%.1s' {} + | wc -c, but that potentially runs more commands than the grep -c / approach and is less efficient as it means holding the data and running potentially huge command lines. – Stéphane Chazelas Aug 2 '16 at 9:22
  • I think ls -A1 |wc -l is a bit more intuitive for the ls options and will probably be a bit faster. Bring the steak sauce. – flickerfly Mar 7 '17 at 15:01
  • @flickerfly, that counts all directory entries but . and .. not only the regular files or non-directory files. That also doesn't work properly for file names with newline characters. Note that -1 is superflous with POSIX compliant ls implementations. – Stéphane Chazelas Mar 7 '17 at 15:26
3

To exclude subdirectories

find . -maxdepth 1 -type f | wc -l

This assumes that none of the filenames contain newline characters, and that your implementation of find supports the -maxdepth option.

  • 4
    That doesn't work if there are filenames with newline characters. Replace wc -l with grep -c /. You can't assume a file path to be a line of text, you can't even assume it to be text at all as generally on Unix-like systems, file paths are just arrays of non-0 bytes, with nothing enforcing those bytes to form valid characters in the current locale. In that regard grep -c / is not even guaranteed to work (though in practice it generally does) as grep is only required to work properly on valid text. – Stéphane Chazelas Aug 1 '16 at 10:30
  • @StéphaneChazelas given that the context is an assignment this Answer is almost certainly sufficient. – roaima Aug 1 '16 at 11:47
  • 5
    @roaima, on the contrary, if that's about teaching, you want to teach the right thing and good practice. We don't want the next generation of coders making those incorrect assumption and write unreliable/vulnerable code. – Stéphane Chazelas Aug 1 '16 at 11:51
  • Not sure of stackexchange etiquette, but choosing not to edit my answer in response to @StéphaneChazelas comment, as his own answer covers this improvement and more. – user4556274 Aug 1 '16 at 11:58
  • 1
    @roaima. I don't agree. What first grades learn about arithmetic is correct. They don't learn that 1+1=2 only to learn later that it's incorrect and that they should forget about it. Here, find . | wc -l is incorrect or at least is only correct if you make some assumptions (which in practice you can rarely enforce). So, at the very least, those assumptions should be stated here. But IMO, it's better to teach a correct solution instead so people don't have to unlearn what they've be taught before when they move to a more advanced grade. – Stéphane Chazelas Aug 1 '16 at 14:37
3

To iterate over everything in the current directory, use

for f in ./*; do

Then use either

[ -f "$f" ]

or

test -f "$f"

to test whether the thing you got is a regular file (or a symbolic link to one) or not.

Note that this will exclude files that are sockets or special in other ways.

To just exclude directories, use

[ ! -d "$f" ]

instead.

The quoting of the variable is important as it would otherwise miscount if there, for example, exists a directory called hello world and a file called hello.

To also match hidden files (files with a leading . in their filenames), either set the dotglob shell option with shopt -s dotglob (in bash), or use

for f in ./* ./.*; do

It may be wise also to set the nullglob shell option (in bash) if you want the shell to return nothing when these glob patterns match nothing (otherwise you'll get the literal strings ./* and/or ./.* in f).

Note: I avoid giving a complete script as this is an assignment.

  • @JeffSchaller Thanks. I think I fixed that now. – Kusalananda Aug 1 '16 at 10:01
  • 1
    Quoting only disables the split+glob operator, it does nothing to change the meaning of - (unless - is in $IFS). test -f -x or test -f "-x" are the same thing (and are generally not a problem except in very old shells, but then you'd solve the problem with for f in ./*, not with quoting). Also note that test -f hello world would give you an error regardless of whether hello is a regular file or not (except in AT&T ksh) – Stéphane Chazelas Aug 1 '16 at 12:22
  • @StéphaneChazelas I should write all of these things down... Thanks again! I'll remove the passage about - as I'm bypassing it with ./*. – Kusalananda Aug 1 '16 at 12:26
  • Why not use dotglob to match hidden files as well? – Konrad Rudolph Aug 1 '16 at 13:26
  • @KonradRudolph You know, that's a good suggestion. In my own scripts (and I rarely write bash scripts) I tend to not rely on shopts very much as they change the behaviour of basic things so radically. But in this case, just for doing this, it's a viable alternative. I'll mention it. – Kusalananda Aug 1 '16 at 13:31
2
#!/bin/bash

# initialize counter
count=0;

# go through the whole directory listing, including hidden files
for name in * .*
do
    if [[ ! -d $name ]]
    then
        # not a directory so count it
        count=$(($count+1))
    fi
done

echo $count
  • Just added support for hidden files. – Julie Pelletier Aug 1 '16 at 15:36
1

If you want something that will handle files containing embedded newlines and other unprintable characters, this solution will work. But it's almost certainly overkill for your assignment.

find . -maxdepth 1 -type f -print0 | tr -dc '\0' | wc -c

What this does is to discard everything apart from the trailing \0 at the end of each filename. It then counts those characters, which gives the number of files.

1

Here's a bash script that will count up and then echo the number of (non-directory) files in the current directory:

#!/usr/bin/env bash
i=0
shopt -s nullglob dotglob
for file in *
do
  [[ ! -d "$file" ]] && i=$((i+1))
done
echo "$i"

Setting "nullglob" gets the count right when there are no files (hidden or otherwise) in the current directory; leaving nullglob unset would mean that the for loop would (incorrectly) see one item: *.

Setting "dotglob" tells bash to also include hidden files (those start with a .) when globbing with *. By default, bash will not include . or .. (see: GLOBIGNORE) when generating the list of hidden files. If your assignment does not want to count hidden files, then do not set dotglob.

-1

I believe the simplest way to count the files and directories in the current directory is:

$ ls | wc -w

-w: is used to count words (not lines as the -l does) because ls outputs all files and directories as an empty-space separated list of words.

And to count only the files :
You can playing a little-bit with the marvelous ls flags you can get the directories out by adding a forward slash to directories ls -p make ls print the files in lines with -l and then invert grep to filter out the forward slashes:

$ ls -pl | grep -v '/' | wc -l
  • 1
    That last one will definitely be wrong if any filenames contains newlines. It will also count the "header" (total NN) that ls -l prints. – Kusalananda Aug 1 '16 at 17:24
  • 1
    And the first if there are newlines or spaces in filenames. – Kusalananda Aug 1 '16 at 17:27
  • True my friend I haven't thought of that! – Angelos Asonitis Aug 2 '16 at 15:47
-3

Your command will include sub-directories.

I'd go with:

count=0
for file in $(ls -a); do 
  if [ -f $file ]; then 
    count=$(expr $count + 1)
  fi
done
echo "There are $count files on $(pwd)"
-3

I usually stick to the **ls** command. It gives me all kinds of possibilities.

A basic example :

ls | wc -l 

Will list out the count of all files and directories in a path

ls -R | wc -l 

Will list out the count of all files and directories in a path and its sub-directories.

ls -R *.log | wc -l

The count of only log files in the path and also all the sub-directories.

ls -la *.log | wc -l

Will give you only the log files count in the current directory and not sub-directories.

You limit here is your ls command

'ls' also gives us the ability to filter out files only by certain date, by file formats, and a lot more, using wc in par with 'ls', is possibly the best robust solution I would consider.

Have a look at 'man ls'

  • @Kusalananda, ls -aq | wc -l would be OK as then filenames are guaranteed to be on single lines (since a \n in the filename is rendered as ?). So the first example is almost right. The rest have more issues. – Stéphane Chazelas Aug 1 '16 at 12:41
  • @StéphaneChazelas My comment was in this case a comment on the recommendation to use ls for these sort of things. It's not the tool for the job really. – Kusalananda Aug 1 '16 at 12:47

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