1.
The first one:
for f in *; do
echo "$f"
done
fails for files called -n
, -e
and variants like -nene
and with some bash deployments, with filenames containing backslashes.
The second:
find * -prune | while read f; do
echo "$f"
done
fails for even more cases (files called !
, -H
, -name
, (
, file names that start or end with blanks or contain newline characters...)
It's the shell that expands *
, find
does nothing but print the files it receives as arguments. You could as well have used printf '%s\n'
instead which as printf
is builtin would also avoid the too many args potential error.
2.
The expansion of *
is sorted, you can make it a bit faster if you don't need the sorting. In zsh
:
for f (*(oN)) printf '%s\n' $f
or simply:
printf '%s\n' *(oN)
bash
has no equivalent as far as I can tell, so you'd need to resort to find
.
3.
find . ! -name . -prune ! -name '.*' -print0 |
while IFS= read -rd '' f; do
printf '%s\n' "$f"
done
(above using a GNU/BSD -print0
non-standard extension).
That still involves spawning a find command and use a slow while read
loop, so it will probably be slower than using the for
loop unless the list of files is huge.
4.
Also, contrary to shell wildcard expansion, find
will do a lstat
system call on each file, so it's unlikely that the non-sorting will compensate for that.
With GNU/BSD find
, that can be avoided by using their -maxdepth
extension which will trigger an optimization saving the lstat
:
find . -maxdepth 1 ! -name '.*' -print0 |
while IFS= read -rd '' f; do
printf '%s\n' "$f"
done
Because find
starts outputting file names as soon as it finds them (except for the stdio output buffering), where it may be faster is if what you do in the loop is time consuming and the list of file names is more than a stdio buffer (4/8 kB). In that case, the processing within the loop will start before find
has finished finding all the files. On GNU and FreeBSD systems, you may use stdbuf
to cause that to happen sooner (disabling stdio buffering).
5.
The POSIX/standard/portable way to run commands for each file with find
is to use the -exec
predicate:
find . ! -name . -prune ! -name '.*' -exec some-cmd {} ';'
In the case of echo
though, that's less efficient than doing the looping in the shell as the shell will have a builtin version of echo
while find
will need to spawn a new process and execute /bin/echo
in it for each file.
If you need to run several commands, you can do:
find . ! -name . -prune ! -name '.*' -exec cmd1 {} ';' -exec cmd2 {} ';'
But beware that cmd2
is only executed if cmd1
is successful.
6.
A canonical way to run complex commands for each file is to call a shell with -exec ... {} +
:
find . ! -name . -prune ! -name '.*' -exec sh -c '
for f do
cmd1 "$f"
cmd2 "$f"
done' sh {} +
That time, we're back to being efficient with echo
since we're using sh
's builtin one and the -exec +
version spawns as few sh
as possible.
7.
In my tests on a directory with 200.000 files with short names on ext4, the zsh
one (paragraph 2.) is by far the fastest, followed by the first simple for i in *
loop (though as usual, bash
is a lot slower than other shells for that).
find
doesn't open the files it finds. The only thing I can see biting you here with respect to a large number of files is ARG_MAX. – kojiro Oct 22 '13 at 13:22read f
will mangle file names as it reads them (e.g. names with leading blanks). Alsofind * -prune
seems to be a very convoluted way to say simplyls -1
yes? – Ian D. Allen Oct 22 '13 at 14:20find .
, notfind *
. – alexis Oct 22 '13 at 14:22ls -l
is a bad idea. But parsingls -1
(that's a1
not anl
) is no worse than parsingfind * -prune
. Both fail on files with newlines in the names. – Ian D. Allen Oct 22 '13 at 14:48find .
andfind *
do not give the same results with the-prune
expression. – Ian D. Allen Oct 22 '13 at 14:50*
is just an example. could be alsofind 'something*'
. So we need two things here: 1. handling: a way to handle all special characters on all different environments. And more interesting: 2. performance: the differences between for and find using wildcards like * opposing using find with a direct path like "./" (without -prune then) – rubo77 Oct 22 '13 at 15:38find 'something*'
? You do realize that the argument offind
gives places to search, not things to search for? – alexis Oct 22 '13 at 15:53