2

I want to iterate over a list of files in Bash and perform some action. The problem: the file names may contain whitespace, which creates an obvious problem with wildcards or ls:

touch a\ b
FILES=* # or $(ls)

for FILE in $FILES; do echo $FILE; done

yields

a
b

Now, the conventional way to handle this is to use find … -print0 instead. However, this only works (well) in conjunction with xargs -0, not with Bash variables / loops.

My idea was to set $IFS to the null character to make this work. However, the comp.unix.shell seems to think that this is impossible in bash.

Bummer. Well, it’s theoretically possible to use another character, such as : (after all, $PATH uses this format, too):

IFS=$':'
FILES=$(find . -print0 | xargs -0 printf "%s:")

for FILE in $FILES; do echo $FILE; done

(The output is slightly different but fair enough.)

However, I can’t help but feel that this is clumsy and that there should be a more direct way of doing this. I’m looking for a more direct way of accomplishing this, preferably using wildcards or ls.

Konrad Rudolph
  • 530,221
  • 131
  • 937
  • 1,214

3 Answers3

4

The best way to handle this is to store the file list as an array, rather than a string (and be sure to double-quote all variable substitutions):

files=(*)
for file in "${files[@]}"; do
    echo "$file"
done

If you want to generate an array from find's output (e.g. if you need to search recursively), see this previous answer.

Community
  • 1
  • 1
Gordon Davisson
  • 118,432
  • 16
  • 123
  • 151
  • Can you tell me what the advantage of this method over Collin’s is? – Actually, for my specific purpose there’s a pretty obvious advantage since I also need the *count* of the files, which is rather easier to obtain with arrays. But apart from that, what’s the advantage? – Konrad Rudolph Apr 18 '12 at 15:04
  • Mine works. Collin's only appears to; it's actually just storing the wildcard, not a list of files. – Gordon Davisson Apr 18 '12 at 15:13
  • I can’t follow. Collin’s method works just fine for me. That he stores the wildcard doesn’t matter, does it? That’s just how variables in Bash work anyway. – Konrad Rudolph Apr 18 '12 at 15:16
  • Maybe I should've said "mine works the way I expect it to." I guess it comes down to what you're trying to do: if you want to store a list of files, use mine; if you want to store a file pattern (and generate a list of matching files later), use Collin's. But if you use Collin's as though it stored a list of files, you're going to have trouble: things like the "list" changing as files are added and removed, or as you `cd` around, etc. Counting entries is another example: a pattern matches any number of files, while a list contains a specific number of entries. – Gordon Davisson Apr 18 '12 at 15:32
  • Thanks for explaining the difference, I wasn't entirely sure what was going on under the hood, but could tell the glob worked :-P – Collin Apr 18 '12 at 17:31
1

Exactly what you have in the first example works fine for me in Msys Bash, Cygwin and on my Fedora box:

FILES=*
for FILE in $FILES
do
    echo $FILE
done
Collin
  • 11,977
  • 2
  • 46
  • 60
  • This isn't actually storing the file list in the variable, just the unexpanded wildcard. `FILES=*; echo "$FILES"` prints `*`, not a list of files. – Gordon Davisson Apr 18 '12 at 15:11
-2

Its very important to preceed

IFS=""

otherwise files with two directly following spaces will not be found