2

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
4
  • 1
    I'd use this method instead: while IFS= read -r line; do echo rm -R "$line"; done < <(mdfind -name iMazing) Run it first with the echo command to see you like the output, then remove the echo from the compound command and run it again. Note that with the echo command the output will not be quoted but it will be when run without the echo command. May 31, 2021 at 22:30
  • @user3439894 why does this method work? I tried setting IFS=' ' but that still didn't make my for loop work, I'm just interested
    – Nickotine
    May 31, 2021 at 22:31
  • in fact even without IFS your cmd works while read -r line; do ls "$line"; done < <(mdfind -name iMazing)
    – Nickotine
    May 31, 2021 at 22:33
  • 1

3 Answers 3

7

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:

3
  • I remember using IFS=$'\n' in bash scripts where vars had spaces, I'm going to give you the answer since you fixed my for loop but if you could explain how the while read -r line loop works without IFS that would be a nice bonus :)
    – Nickotine
    May 31, 2021 at 23:16
  • 1
    @Nick For IFS in the read case, this is explained in the linked Unix.SE Q&A: "if you don't need any of the preceding and trailing whitespace characters you could just write read -r line and be done with it."
    – grg
    May 31, 2021 at 23:23
  • 2
    The safest way to do it is with null-delimited entries; use while IFS= read -r -d '' line and done < <(mdfind -0 -name iMazing). The read -d '' and mdfind -0 are the critical changes. See BashFAQ #20 for some similar examples. May 31, 2021 at 23:50
6

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).

3

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
4
  • what’s the benefit of this over while read -r line? Seems like it just gives you more to do but I’m interested if you can describe a case where it’s more useful
    – Nickotine
    Jun 1, 2021 at 3:38
  • 1
    @Nickotine The main advantage is that you can do things other than iterate over the list once. So if you want use it multiple times, process it, etc. BTW, there's a also a null-delimited (safer) version of this, provided you have bash v4.4 or later: mapfile -d '' -t files < <(mdfind -0 -name iMazing) Jun 1, 2021 at 5:24
  • @gordonDavisson I like the idea I could use it for other stuff, thank you. I have bash 5 which I got with brew, but for this case since I’m deleting the files I don’t think I need it, but it’s a pretty cool feature :)
    – Nickotine
    Jun 1, 2021 at 6:13
  • @Nickotine In that case, it's even easier -- just use xargs. I'll submit an answer in a minute. Jun 1, 2021 at 7:14

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .