Looking for a way in AppleScript to get the contents of a directory with an exclusion to parse through. I know how to do this using find
in a for loop with the command:
find . -not -name "*.bat"
but I'm curious to know if there is an AppleScript only approach to getting a folder's contents without the file extension .bat
.
I've built a dummy directory on my desktop, added several files and tried:
tell application "Finder"
set theFiles to get every item of (entire contents of folder (choose folder)) whose kind ≠ ".bat"
reveal theFiles
end tell
the .bat
files are highlighted. Using does not contain
compiles but errors out to:
Error:
Can’t get every item of true.
Code:
tell application "Finder"
set theFiles to get every item of (entire contents of folder (choose folder) does not contain ".bat")
reveal theFiles
end tell
Trying another approach I get an error of:
Error:
Finder got an error: Unknown object type.
Code:
tell application "Finder"
set theFiles to get every item of (entire contents of folder (choose folder)) whose type ≠ {"public.bat"}
reveal theFiles
end tell
If I try to modify the approach I can only get a solid result if I narrow down the name of a bat file using:
Code:
tell application "Finder"
set theFiles to get (every file of folder (choose folder) whose name is not in {"foobar.bat"})
reveal theFiles
end tell
In pure AppleScript is there a way to get contents of a directory with an exclusion for file type? I would possibly like to chain the exclusion for multiple file types.
Best Answer
Finder
Here are a few different ways to filter by file extension, with a brief description of their benefits. For the purposes of illustration, I've had Finder operations performed upon an open Finder window (my Pictures folder), which has many jpegs in it that serve as a good sample against which to test exclusion filters.
This took 14 seconds on its second run, to provide a baseline for rough comparisons.
The one simple act of coercing the result to an
alias list
consistently sped up the performance time to 8 seconds (almost twice as fast):Surprisingly to me, filtering by
name extension
didn't seem to affect performance times compared to using thename
property, possibly suggesting both are performing equivalent string comparisons under the hood:Nonetheless, I would preferentially choose to filter by
name extension
as a matter of personal choice, because it's cleaner syntax.One benefit of Finder is that we can condense multiple filters into one, inclusive expression that essentially forms a list of predicates:
and this was just as fast for two extensions than it was for one, and then again for four:
Filtering by the
name
property can't be done with multiple extensions in this manner, so must be done with separate clauses for each extension you wish to exclude:System Events
System Events is much more equipped to handle file processing and filtering than Finder, which is somewhat counterintuitive. It also prevents Finder being blocked during the operation.
You cannot use a list of predicates as you can with Finder, so we have to make do with the longer expressions:
But the fact that this operation takes approximately zero seconds makes it worthwhile. Note here that I asked System Events to return the
path
to these items, simply so I could then pass the result to Finder and have it select them as before (this is also how you can usereveal
after a System Events call).Objective-C
If you need to search huge folders or a nested list of folders, don't be tempted to use Finder's
entire contents
property, or it will make you cry. Use AppleScriptObjC, which performs deep searches of folder trees with performance times that are faster than System Events and shell. It comes at a cost of slightly more overhead compared to System Events/Finder, but probably comparable to that of calling a shell script.First, it's best to define some handlers to take care of the file searching and filtering:
Then, you can perform a call like this:
Note that Objective-C will perform case-sensitive searches, so it's prudent to include file extensions to be excluded in both upper- and lowercase formats. The empty string excludes folders (except when the folder name contains a period).
Objective-C performed a deep enumeration of my Pictures folder filtering out all jpegs to return a list of 643 files nested within that directory, and it did this in approximately zero seconds. Neither Finder nor System Events can match this time (Finder won't respond after a while, and doing a deep search with System Events requires manually iterating through child folders, and deciding how to handle the nested list it returns, but it does it in an impressive 4 seconds). The shell is just as fast for this number of files, but I am fairly confident from what I've read that Objective-C will out perform the shell's
find
command for large volumes of files.You'll see I commented out the Finder's
reveal
command: asking Finder to reveal 643 files in different folders was...um...unpleasant. I don't recommend it.Conclusion
① Utilise System Events to perform file searches and optionally retrieve file paths, which can be utilised for any operations you wish Finder to subsequently perform upon them.
② If you do, for some reason, need to use Finder to perform the file search, always coerce to
alias list
for performance benefits and a class of item that's universally much easier to handle later.③ For large or nested folder structures, use Objective-C.