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.
9 Answers 9
If you want only regular files,
With GNU find
:
find . -maxdepth 1 -type f -printf . | wc -c
Other find
s:
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 find
s, 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 (withwc -l
)? Granted, thedirname
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 doingfind . ...
, doesn’t it? I would suggest-exec stat -c "" -- {} +
, butstat
isn’t in POSIX either (right?).G-Man Says 'Reinstate Monica'– G-Man Says 'Reinstate Monica'2016年08月01日 23:31:18 +00:00Commented Aug 1, 2016 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 forgrep
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– Stéphane Chazelas2016年08月02日 09:16:45 +00:00Commented Aug 2, 2016 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 GNUfind
had a much better interface (with the-printf
syntax different from GNUstat
's (!?)) long before GNUstat
was created. Here you could dofind . -exec printf '%.1s' {} + | wc -c
, but that potentially runs more commands than thegrep -c /
approach and is less efficient as it means holding the data and running potentially huge command lines.Stéphane Chazelas– Stéphane Chazelas2016年08月02日 09:22:08 +00:00Commented Aug 2, 2016 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– flickerfly2017年03月07日 15:01:34 +00:00Commented Mar 7, 2017 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 compliantls
implementations.Stéphane Chazelas– Stéphane Chazelas2017年03月07日 15:26:44 +00:00Commented Mar 7, 2017 at 15:26
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.
-
4That doesn't work if there are filenames with newline characters. Replace
wc -l
withgrep -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 regardgrep -c /
is not even guaranteed to work (though in practice it generally does) asgrep
is only required to work properly on valid text.Stéphane Chazelas– Stéphane Chazelas2016年08月01日 10:30:03 +00:00Commented Aug 1, 2016 at 10:30 -
@StéphaneChazelas given that the context is an assignment this Answer is almost certainly sufficient.Chris Davies– Chris Davies2016年08月01日 11:47:43 +00:00Commented Aug 1, 2016 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– Stéphane Chazelas2016年08月01日 11:51:48 +00:00Commented Aug 1, 2016 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– user45562742016年08月01日 11:58:01 +00:00Commented Aug 1, 2016 at 11:58
-
2@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– Stéphane Chazelas2016年08月01日 14:37:04 +00:00Commented Aug 1, 2016 at 14:37
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.2016年08月01日 10:01:07 +00:00Commented Aug 1, 2016 at 10:01
-
1Quoting only disables the split+glob operator, it does nothing to change the meaning of
-
(unless-
is in$IFS
).test -f -x
ortest -f "-x"
are the same thing (and are generally not a problem except in very old shells, but then you'd solve the problem withfor f in ./*
, not with quoting). Also note thattest -f hello world
would give you an error regardless of whetherhello
is a regular file or not (except in AT&T ksh)Stéphane Chazelas– Stéphane Chazelas2016年08月01日 12:22:32 +00:00Commented Aug 1, 2016 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./*
.2016年08月01日 12:26:33 +00:00Commented Aug 1, 2016 at 12:26 -
Why not use
dotglob
to match hidden files as well?Konrad Rudolph– Konrad Rudolph2016年08月01日 13:26:33 +00:00Commented Aug 1, 2016 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 onshopts
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.2016年08月01日 13:31:27 +00:00Commented Aug 1, 2016 at 13:31
#!/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– Julie Pelletier2016年08月01日 15:36:51 +00:00Commented Aug 1, 2016 at 15:36
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.
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.
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
-
1That last one will definitely be wrong if any filenames contains newlines. It will also count the "header" (
total NN
) thatls -l
prints.2016年08月01日 17:24:59 +00:00Commented Aug 1, 2016 at 17:24 -
1And the first if there are newlines or spaces in filenames.2016年08月01日 17:27:01 +00:00Commented Aug 1, 2016 at 17:27
-
True my friend I haven't thought of that!Angelos Asonitis– Angelos Asonitis2016年08月02日 15:47:46 +00:00Commented Aug 2, 2016 at 15:47
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)"
-
2See unix.stackexchange.com/questions/128985/why-not-parse-ls2016年08月01日 09:13:44 +00:00Commented Aug 1, 2016 at 9:13
-
@Kusalananda,
ls
only does (may do) that when the output goes to a tty device. Here, it's going to a pipe or socketpair depending on the shell. The issues would be more about filenames containing IFS or glob characters (try aftertouch '*' 'a b'
for instance.Stéphane Chazelas– Stéphane Chazelas2016年08月01日 12:46:15 +00:00Commented Aug 1, 2016 at 12:46 -
@StéphaneChazelas Thanks for putting me straight again.2016年08月01日 12:49:56 +00:00Commented Aug 1, 2016 at 12:49
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'
-
See: unix.stackexchange.com/questions/128985/why-not-parse-ls2016年08月01日 12:03:53 +00:00Commented Aug 1, 2016 at 12:03
-
@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– Stéphane Chazelas2016年08月01日 12:41:32 +00:00Commented Aug 1, 2016 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.2016年08月01日 12:47:49 +00:00Commented Aug 1, 2016 at 12:47
find
not to descend into subdirectories. The-maxdepth 1
will do that for you.find
-based answers do not explicitly increment a counter -- is that a requirement?