4 Answers
4

As said many time on this site, leaving a variable expansion (as in $var) or command substitution (as in `cmd` or $(cmd)) (or arithmetic expansion (as in $((11 * 11))) in most shells) unquoted in Bourne/POSIX shells is the split+glob operator.

The content of $var or the output of cmd (without the trailing newline characters) is split according to the current value of the $IFS special variable (which by default contains the SPC, TAB, and NL characters (and NUL in zsh)) and each word resulting of that splitting is subject to filename generation also known as globbing.

For instance, if find ouputs ./foo bar.pdf\n./*foo*\tbar.pdf\n (\t meaning TAB and \n NL), with the default value of $IFS, the command substitution will expand to ./foo bar.pdf\n./*foo*\tbar.pdf (trailing newline removed), and then be split into ./foo, bar.pdf, ./*foo*, and foo.pdf and ./*foo* which is a wildcard pattern will be expanded into as many arguments as there are non-hidden files in the current directory whose name contains foo.

If you want to split on newline characters only, you need to set $IFS to newline only:

IFS='
'

If you don't want the wildcard patterns to be expanded, you need to disable it with

set -f

However note that newline is as valid a character as any in a file name, so more generally, the find -print output cannot be post-processed reliably.

An output like:

./a.pdf
./b.pdf

either means the a.pdf and b.pdf files in the current directory, or the file called b.pdf in the a.pdf\n. directory.

Some find implementations like GNU find (where it originated from) have a -print0 predicate to output the filename followed by a NUL character instead of a NL character. With standard find, you can use -exec printf '%s\0' {} + with the same result. NUL is the only character that cannot occur in a filename.

However, zsh is the only shell that can store a NUL character in its variables (like the $IFS character), so:

IFS=$'\0'
for i in `find ... -print0`; do
...
done

(no need for set -f in zsh since zsh doesn't do globbing upon command substitution) will work in zsh but not in other shells.

Best, and portably, is to have find call the commands you want to run on those files. As @Gnouc suggests:

find . -name '*.pdf' -exec the command {} \;

If you need anything more complex involving shell statements, you can still do things like:

zsh (since 1990) has most of the functionality of find included in its globbing capabilities through a syntax where you can specify any level of subdirectories ((*/)# syntax or its simpler form **/) and globbing qualifiers (which are the pendant of the -type f, -mtime, -perm... in find).

The **/ part of that was copied by ksh93 in 2003, fish in 2005, bash in 2009 and tcsh in 2010 (though tcsh also copied the ***/ part). And all of them do not enable it by default. Unfortunately, note that both bash and fish** do follow symlinks to directories (like -L/-follow in find, or *** in zsh or tcsh).

In those shells, you can find pdf files in any level of subdirectories without having to rely on find, but note that caveat above about fish and bash, and only zsh has the globbing qualifiers.

Unfortunately, bash's **/ follows symlinks when descending the directory tree, which is generally not what you want, you may want to use ksh93's one (which bash copied it from and doesn't have the issue) or even better zsh's one (which ksh93 copied it from) where you can have most of the functionality of find through its globbing qualifiers.
–
Stéphane ChazelasJun 13 '13 at 19:14

Do you try my solution? Do you know the meaning of {} in find command?
–
cuonglmJun 13 '13 at 19:10

@terdon, the point of Gnouc's is that find will take care of the looping (using loops in shell is generally bad practice) and will call any command you want it to run, here echo like in the OP's question. Granted, echo is a bad choice of commands, but that's the command the OP was using.
–
Stéphane ChazelasJun 13 '13 at 19:12

@StephaneChazelas I took the question to be asking how to safely store a file name with spaces in a bash variable. I imagine the OP is doing something more complex then echo pathname $foo. This solution is identical to what the OP is doing and its output can still not be saved in a variable.
–
terdon♦Jun 13 '13 at 19:18

@Gnouc I get what you mean to do with -exec but you still can't use this to save find's output in a variable. Granted if all the OP wants is to print the string pathname before the actual path, then this is all you need. But not if the intention is more complex than what -exec can handle. And yes, I know what {} is :).
–
terdon♦Jun 13 '13 at 19:22