This question already has an answer here:

A simple for loop in Unix would be:

for FILE in $BASE_WORK_DIR/*.pdf
     echo $FILE

This will echo all .pdf files inside BASE_WORK_DIR directory.

What if BASE_WORK_DIR contains sub directories as well which also contain the pdf file.

In that case how can I design my for loop to take all pdf files from BASE_WORK_DIR as well as sub directories of BASE_WORK_DIR ?

marked as duplicate by Anthon, Zelda, slm, strugee, Karlson Jan 22 '14 at 20:25

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.

  • 1
    All the methods you should use are covered here: Looping through files with spaces in the names?. – slm Jan 22 '14 at 14:01
  • The fact that this question is also answered there doesn't mean that it's the same question -- to the contrary, in fact. Leaving this open. – Shadur Jan 22 '14 at 17:59

The standard and canonical and reliable syntax is:

find . -type f -name '*.pdf' -exec sh -c '
  for f do
    something with "$f"
  done' sh {} +

(note that it may run several sh invocation if the list of files is very big).

With zsh, the equivalent would be

for f (./**/*(.NDoN)) {
  something with "$f"

(. for -type f, D to include hidden files, oN to not bother sorting the list, N to not complain if there's no matching file), except that you wouldn't get error messages for the directories you don't have access to.

  • Surely the caveat of the sh version is that you cannot set variables in the main part of the script from inside the loop? – Graeme Jan 22 '14 at 15:59
  • @Graeme, yes, though you could pass them back to the calling shell using export -p. – Stéphane Chazelas Jan 22 '14 at 16:12

In bash4, and following symlinks to directories is desirable, you can enable globstar and use **:

shopt -s globstar
for file in "$base_work_dir"/**/*.pdf
  echo "$file"

Otherwise in an sh script, find is probably the best way:

set -f
for file in $(find "$base_work_dir" -name *.pdf)
  echo "$file"

(add the -L option to find to follow symlinks like with bash globstar)

Note that you will have issues here if any filenames contain newlines.

  • Better use ksh93 -o globstar or zsh whose ** don't have the symlink issue. – Stéphane Chazelas Jan 22 '14 at 15:26
  • @Stephane why is the set -f necessary when the *.pdf is quoted? – Graeme Jan 22 '14 at 15:39
  • In every shell but zsh globbing is performed upon command substitution, so for instance if there's a file called *.pdf in $base_work_dir/foo, all the PDFs in foo except *.pdf will be counted twice. Leaving a variable or command substitution unquoted is the split+glob operator. If you don't want the glob part, you need set -f. – Stéphane Chazelas Jan 22 '14 at 15:45

Another solution with find that would solve the problem of newlines in file names:

find "$BASE_WORK_DIR" -name '*.pdf' -print0 |
  while IFS= read -d '' -r file;do
    # your magic here

Caveat emptor: this only works in Zsh and Bash.

-print0 only works in a few find implementations like GNU find. Use -exec printf '%s\0' {} + if your find doesn't support it.

  • The $'\0' and -d option for read will work in bash only. – Graeme Jan 22 '14 at 13:11
  • Lol, that would have been an interesting one to debug further down the line. – Graeme Jan 22 '14 at 13:21
  • @slm I did not know that. Thanks :). – Joseph R. Jan 22 '14 at 14:03
  • 1
    @JosephR, bash doesn't support passing NUL characters in arguments (contrary to zsh), $'\0' is '' in bash. – Stéphane Chazelas Jan 22 '14 at 15:21

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