Hazel deletes custom file icons, and a workaround

I use Hazel extensively for automating file management tasks on my macOS systems. Recently I found that Hazel aggressively matches an invisible system file that appears whenever you use a custom file or folder icon. I’ll describe the problem and present a workaround.

In a handful of directories, I have a rule that prevents users (me) from adding certain file types. So the rule just matches any file that is not an image, for example, and deletes it. This is all well and good until to try to add a custom icon to this directory. Since the file Icon? that gets created as a result is not an image, the Hazel rule dutifully deletes it.

In my first try to fix it, I tried to match the filename on Icon? but that didn’t work because the actual name is Icon$'\r', Thinking this special character \r would be too hard to match, I moved onto plan B.

Plan B in this case is to match on a particular bit of file metadata, the kMDItemDisplayName key. This is what Finder displays, but is not the actual file name, thus explaining Hazel’s inability to match it properly. So if we run:

mdls -n kMDItemDisplayName Icon$'\r'
# prints kMDItemDisplayName = "Icon?"

Now, we just need to strip the double quotes and match. So before we put this into a rule, with a Passes shell script criterion, let’s think about what we’re trying to do and how the logic of this criterion works. The criterion is considered positive when it returns a 0 exit code. But in our case, we are setting up a rule that says “If the file is not an image and is not a custom icon file, then delete it." So if the file matches Icon? then we need to return 1 to negate the second clause of this rule. Here’s how to do it:

R=$(mdls -n kMDItemDisplayName "$1" | cut -d ' ' -f 3 | tr -d \")
R=$(echo "$R" | tr '?' 'x')
[[ "$R" =~ ^Iconx$ ]] && exit 1 || exit 0


  1. cut -d ' ' -f 3 cuts the result of the previous command into fields separated by the space character and returns the third field of that list.
  2. tr -d \" strips the quotes from the resulting string.
  3. In the next line tr '?' 'x' changes all ? characters to x because that makes regex matching in the next line easier. Bash regular expressions are quite limited, so it’s easier this way.

There may be a different/better way, but this works!