How do I stop word splitting in bash for loop?

When I run the command below:

mdfind -name iMazing

I get the following output:

/Users/apple/Library/Application Support/MobileSync/Backup/iMazing.Versions
/Users/apple/Library/Application Support/com.DigiDNA.iMazing2Mac

I want to run a for a loop to delete these dirs.

Before doing so I tested with ls:

for i in $(mdfind -name); do ls "$i"; done

Output:

ls: /Users/apple/Library/Application: No such file or directory
ls: Support/MobileSync/Backup/iMazing.Versions: No such file or directory
ls: /Users/apple/Library/Application: No such file or directory
ls: Support/com.DigiDNA.iMazing2Mac: No such file or directory

As you can see word splitting has happened because of the space in Application Support

I tried the following variations:

"${i}"

Escaping the quotes:

"\"$i"\"

"\"${i}"\"

to no avail, any ideas?

EDIT:

So IFS does not matter, what's going on?:

 kn: (master)  
 λ  IFS=; for i in $(mdfind -name iMazing); do ls "$i"; done
ls: /Users/apple/Library/Application Support/MobileSync/Backup/iMazing.Versions
/Users/apple/Library/Application Support/com.DigiDNA.iMazing2Mac: No such file or directory
  kn: (master)  
 λ  while IFS= read -r i; do ls "$i"; done < <(mdfind -name iMazing)
Blueprints  Readme.txt  Temp        Trash       Versions
com.microsoft.appcenter
  kn: (master)  
 λ  while read -r i; do ls "$i"; done < <(mdfind -name iMazing)
Blueprints  Readme.txt  Temp        Trash       Versions
com.microsoft.appcenter

The problem is not the quoting of $i — that's too late.

The for loop has already split the input by spaces, so each for loop iteration is being given one of the four components shown in the errors. You need to fix the loop instead.

You can do this by setting IFS, the Internal Field Separator, to newline.

IFS=$'\n'; for i in $(mdfind -name iMazing); do ls "$i"; done

IFS is how bash determines how to split input. By default, it includes space, so each space in each line of the input is split into two inputs, alongside the existing new lines, causing 4 loops rather than 2. Setting IFS to new line only turns off this splitting by space, preserving each line of input without additional splitting.


A potentially preferable way of looping over lines of output is using read -r line:

while IFS= read -r line; do echo rm -R "$line"; done < <(mdfind -name iMazing)

Unix.SE has a good breakdown of IFS= read -r line:

  • Understanding “IFS= read -r line”

If you just want to pass the results to a single command (not run a sequence of commands, or anything complex like that), you can use xargs. If the command can take a bunch of filenames at once, use something like this:

mdfind -0 -name iMazing | xargs -0 ls

The -0 options make the commands use null bytes as delimiters; since these cannot occur in filenames, this is safe against all the usual confusions.

Note: if you're going to use this with something dangerous, like rm, I recommend running it with something safe, like ls, to make sure it's going to do what you want. (And... you have a good backup, right?) If you want to be really careful, add the -p option (after xargs, but before the command to be executed) and it'll print the command and ask for confirmation before running it.

Also, if the command can't take a bunch of filenames at once, add -n 1 (again, after xargs but before the command).


If you install a version of bash >= 4.0 (say, with Homebrew), you can do:

mapfile -t files < <(mdfind -name iMazing)

which reads the lines of the mdfind output into the "files" array. Then

for file in "${files[@]}"; do ls "$file"; done