Take the 2-minute tour ×
Unix & Linux Stack Exchange is a question and answer site for users of Linux, FreeBSD and other Un*x-like operating systems.. It's 100% free, no registration required.

Recursively iterating through files in a directory can easily be done by:

find . -type f -exec bar {} \;

However, the above does not work for more complex things, where a lot of conditional branches, looping etc. needs to be done. I used to use this for the above:

while read line; do [...]; done < <(find . -type f)

However, it seems like this doesn't work for files containing obscure characters:

$ touch $'a\nb'
$ find . -type f
./a?b

Is there an alternative that handles such obscure characters well?

share|improve this question
1  
find ... -exec bash -c 'echo filename is in \$0: "$0"' {} \; is a better way to do it. –  jw013 Jun 26 at 14:08
    
You can solve this, and keep your original design, by changing your read line to IFS= read -r line. The only character that will break it then is a newline. –  Patrick Jun 26 at 14:43
1  
@Patrick, but filenames can contain newlines. That's why -d $'\0' is preferable. –  godlygeek Jun 26 at 15:14

4 Answers 4

up vote 2 down vote accepted

Yet another use for safe find:

while IFS= read -r -d '' -u 9
do
    [...]
done 9< <( find . -type f -exec printf '%s\0' {} + )

(This works with any POSIX find, but the shell part requires bash. With *BSD and GNU find, you can use -print0 instead of -exec printf '%s\0' {} +, it will be slightly faster.)

This makes it possible to use standard input within the loop, and it works with any path.

share|improve this answer

Doing this is as simple as:

find -exec sh -c 'inline script "$0"' {} \;

Or...

find -exec executable_script {} \;
share|improve this answer

It's a bit difficult to do your read loop portably, but for bash in particular you can try something like this.

Relevant portion:

while IFS= read -d $'\0' -r file ; do
        printf 'File found: %s\n' "$file"
done < <(find . -iname 'foo*' -print0)

That instructs find to print its output delimited by NUL characters (0x00), and read to fetch NUL-delimited lines (-d $'\0') without handling backslashes as escapes for other characters (-r) and not do any word splitting on the lines (IFS=). Since 0x00 is a byte that can't occur in filenames or paths in Unix, this should handle all of your weird filename problems.

share|improve this answer
    
-d '' is equivalent to -d $'\0'. –  l0b0 Jun 26 at 15:53

The simplest (yet safe) approach is to use shell globbing:

$ for f in *; do printf ":%s:\n" "$f"; done 
:a b:
:c
d:
:-e:
:e  f:
h:

To make the above recurse into subdirectories (in bash), you can use the globstar option; also set dotglob to match files whose name begins with .:

$ shopt -s globstar dotglob
$ for f in **/*; do printf ":%s:\n" "$f"; done 
:a b:
:c
d:
:-e:
:e  f:
:foo:
:foo/file1:
:foo/file two:
h:

Beware that up to bash 4.2, **/ recurses into symbolic links to directories. Since bash 4.3, **/ recurses only into directories, like find.

Another common solution is to use find -print0 with xargs -0:

$ touch -- 'a b' $'c\nd' $'e\tf' $'g\rh' '-e'
$ find . -type f -print0 | xargs -0 -I{} printf ":%s:\n" {}
h:/g
:./e    f:
:./a b:
:./-e:
:./c
d:

Note that the h:/g is actually correct since the file name contains a \r.

share|improve this answer

Your Answer

 
discard

By posting your answer, you agree to the privacy policy and terms of service.

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