The usual shell command is find. The options to find are really a small language. Learning how to put the available options together takes time. It may not always be obvious how to modify an existing list of options to add a new search criteria.
find can be faster than a search in Finder, but there are fundamental differences in how they work. find works at the POSIX level and takes no notice of file packages (directories of files that appear as a single item in Finder (e.g. most applications)). Images inside packages will be skipped by the Finder code you were using, but find will ignore the package status of a directory and search inside it. Not all the metadata available to Finder is directly available to find (type, creator, UI positioning info, icon, Get Info comments, displayed name, etc.). Great care must be taken in quoting the arguments to find to make sure nothing is interpreted incorrectly. Also, since find’s output is mostly plain text, care must be taken to make sure that filenames that include newlines or carriage returns are preserved (they are valid file name characters, though Finder usually does a good job dissuading the user from employing them).
The following code uses find to search for files that have extensions in a specified list.
on run
set fileTypes to {"psd", "tif", "jpg", "png", "gif", "bmp", "eps", "ai", "pdf"}
set theItem to alias ((path to desktop as Unicode text) & "stuff:") -- 3496 files in 290 directories; 3248 items (47 packages) in 160 folders
--set theItem to alias ((path to startup disk as Unicode text) & "Applications:Utilities:") -- 12574 files in 5185 directories; 43 items (34 packages) in 6 folders
set t0 to current date
with timeout of 600 seconds
tell application "Finder" to set ImageCount to (every file of entire contents of theItem whose name extension is in fileTypes)
end timeout
set t1 to current date
set foundFiles to findFiles(POSIX path of theItem, fileTypes)
set t2 to current date
{t1 - t0, length of ImageCount, t2 - t1, length of foundFiles}
--> /Applications/Utilities: {0, 0, 12, 1134}: Finder takes less than 1 second to find 0 matching items, find takes 12 seconds to find 1134 matching files
--> ~/Desktop/stuff: {213, 689, 4, 714}: Finder takes 213 seconds to find 689 matching items, find takes 4 seconds to find 714 matching files
end run
to findFiles(startDirectory, extensionList)
if class of extensionList is not list then
set extensionList to {extensionList}
else
(* Duplicate the list, because it will be destructively modified by buildFind0Command.
* We should not stomp on our caller's parameters unless it is clearly documented (like convertPOSIXPathsToFiles and convertItemsToAliases do).
*)
copy extensionList to extensionList
end if
set find0Cmd to buildFind0Command(startDirectory, extensionList)
-- use "true" to ignore the exit code of find, this is needed if there might be any unreadable directories under startDirectory
set cmd to find0Cmd & ";true"
set findResults to do shell script cmd without altering line endings
set posixPaths to extractFind0Results(findResults)
set theFiles to convertPOSIXPathsToFiles(posixPaths)
convertItemsToAliases(theFiles) -- if desired
end findFiles
to buildFind0Command(startDirectory, extensionList)
"find " & quoted form of startDirectory & " -type f " & buildExtensionsClause(extensionList) & " -print0"
end buildFind0Command
to buildExtensionsClause(extensionList)
if length of extensionList is 0 then return ""
repeat with e in extensionList
-- use -iname to match the normal case insensitivity exhibited in Finder
set contents of e to "-iname \\*." & quoted form of quoteForFindNamePredicate(contents of e)
end repeat
if length of extensionList is greater than 1 then
set {otid, text item delimiters} to {text item delimiters, {" -or "}}
try
set extensionClause to "\\( " & (extensionList as Unicode text) & " \\)"
set text item delimiters to otid
on error m number n from o partial result r to t
set text item delimiters to otid
error m number n from o partial result r to t
end try
else
set extensionClause to first item of extensionList
end if
return extensionClause
end buildExtensionsClause
to quoteForFindNamePredicate(nameString)
-- all of "[]*?" should be quoted with a backslash
repeat with c in "[]*?"
set c to contents of c
set nameString to switchText from nameString to ("\\" & c) instead of c
end repeat
nameString
end quoteForFindNamePredicate
(* switchText From: http://bbs.applescript.net/viewtopic.php?pid=41257#p41257
Credit: kai, Nigel Garvey*)
to switchText from t to r instead of s
local d
set d to text item delimiters
try
set text item delimiters to s
set t to t's text items
-- The text items will be of the same class (string/unicode text) as the original string.
set text item delimiters to r
-- Using the first text item (beginning) as the first part of the concatentation means we preserve the class of the original string in the edited string.
tell t to set t to beginning & ({""} & rest)
set text item delimiters to d
on error m number n from o partial result r to t
set text item delimiters to d
error m number n from o partial result r to t
end try
t
end switchText
to extractFind0Results(findResults)
-- break up the null-delimited result of "find -print0"
set {otid, text item delimiters} to {text item delimiters, {ASCII character 0}}
try
set foundPaths to text items of findResults
set text item delimiters to otid
on error m number n from o partial result r to t
set text item delimiters to otid
error m number n from o partial result r to t
end try
-- the output from "find -print0" always ends in a null, so drop the final, always empty entry
if length of foundPaths is greater than 1 then
set foundPaths to items 1 through -2 of foundPaths
else
set foundPaths to {}
end if
foundPaths
end extractFind0Results
to convertPOSIXPathsToFiles(posixPaths)
-- changes the contents of the list passed as posixPaths!
repeat with p in posixPaths
set contents of p to POSIX file (contents of p)
end repeat
posixPaths
end convertPOSIXPathsToFiles
to convertItemsToAliases(theItems)
-- changes the contents of the list passed as theItems!
repeat with i in theItems
set contents of i to contents of i as alias
end repeat
theItems
end convertItemsToAliases
Edit History: Added without altering line endings to do shell script otherwise, line feeds are changed to carriage returns. 2009-01-30: Corrected bug involving inadvertent modification of contents of list passed to findFiles as extensionList. Thanks to Jerome for reporting the problem.