Telling Hazel not to match locked files

Hazel is a centrepiece of my automation suite on macOS. I rely on it to watch directories and take complex actions on files contained within them. Recently I discovered an issue with files that are locked in the Finder. If files that otherwise match all the rules are locked, then Hazel will attempt to execute the rules. But the locked status may preclude execution. For example, I began seeing frequent Hazel notifications popups such as:

Unable to execute the Move action on the matched file, Hazel gives up with a Notification. Instead I would prefer that it not match the file in the first place. But how to do that? There’s no criterion for locked/unlocked file status. Even extended criteria in the “Other…” list seems to have no option for this.

The solution is to use a Passes shell script condition in our rule, using the following script:

ls -lO "$1" | grep -q "uchg" && exit 1 || exit 0

Explanation

  1. The file to be tested is passed to the script as $1 and we are expected to exit with 0 if the file matches and non-zero if the file does not match.
  2. The ls command on Unix system lists directory contents. If we pass a file to ls then is returns only the information about that file. So ls "$1" would just provide basic information about the file. To find any flags on macOS we need two options. The first option -l gives us access to the long format and the -O flag provides file flags. We compress these options into ls -lO.
  3. The flag we’re looking for is uchg so we pipe the ls results to grep which tries to match the uchg flag. We use the -q because we don’t care about the contents of the matching line; we just want to know whether it matches or not.
  4. Finally we exit with 1 (failed match) if uchg is present and 0 if absent. This way files that are locked do not match.

Extra

If you have the macOS developer command line tools installed, you will have getfileinfo installed and you could also use that to find the locked status of a file. For example running getfileinfo -aL "my_unlocked_file" will return 0. Whereas running it on a locked file will return 1.

Possibly there’s a simpler way to accomplish this; but for now this is what I’m going with. Enjoy.

References