Search for file in folders and subfolders

Most likely you have to escape the slash

set thePred to current application's NSPredicate's predicateWithFormat_("(lastPathComponent ==[c] %@) AND (path MATCHES[c] %@)", theName, ("\\/" & theCompanyPrefix))

Regular Expression is much more powerful than standard contains for example the pattern

[format]\/[ABC][123]\s?[/format]

finds every (sub)string which starts with a slash followed by A, B or C followed by 1, 2 or 3 followed by a optional whitespace character

Carl. Having given the matter some more thought, the script proposal I identified as option 1 would not work well. So, I have included below my best suggestion, which seems to meet all your requirements. While I was at it, I included the ability to select several copy-me files in the dialog. I tested this script without issue but let me know if you encounter any issues.

use scripting additions
use framework "Foundation"

on main()
	set searchPath to "/Volumes/Store/Test/" -- this should be path to folder on server
	set sourceFiles to (choose file with multiple selections allowed)
	
	set ATID to AppleScript's text item delimiters
	repeat with aFile in sourceFiles
		set AppleScript's text item delimiters to {":"}
		set sourceFileName to text item -1 of (aFile as text)
		set AppleScript's text item delimiters to {"-"}
		set theCompanyPrefix to text item 1 of sourceFileName
		set theCompanyName to text item 2 of sourceFileName
		set locationFileName to theCompanyName & "-here_i_am.txt"
		set targetFolder to getFiles(searchPath, locationFileName, theCompanyPrefix)
		tell application "Finder" to duplicate aFile to targetFolder with replacing
	end repeat
	set AppleScript's text item delimiters to ATID
end main

on getFiles(theFolder, theName, theCompanyPrefix)
	set theFolder to current application's |NSURL|'s fileURLWithPath:theFolder
	set fileManager to current application's NSFileManager's defaultManager()
	set folderContents to (fileManager's enumeratorAtURL:theFolder includingPropertiesForKeys:{} options:((current application's NSDirectoryEnumerationSkipsPackageDescendants) + (current application's NSDirectoryEnumerationSkipsHiddenFiles as integer)) errorHandler:(missing value))'s allObjects()
	set thePred to current application's NSPredicate's predicateWithFormat_("(lastPathComponent ==[c] %@) AND (path MATCHES[c] %@)", theName, (".*?/" & theCompanyPrefix & ".*"))
	set theFiles to (folderContents's filteredArrayUsingPredicate:thePred)
	if (count theFiles) = 0 then display dialog "A location file was not found for company " & theCompanyPrefix buttons {"OK"} cancel button 1 default button 1 with icon stop
	return ((theFiles's URLByDeletingLastPathComponent) as alias)
end getFiles

main()

Thanks Stefan. I still can’t get that to work–I suspect I’m doing something wrong. I’m working to learn predicates and I’ll include the MATCHES operator in my studies.

Sorry, my bad, unlike NSRegularExpression the MATCHES pattern must cover the entire string, so we have to add wildcard characters at the beginning and the end

set thePred to current application's NSPredicate's predicateWithFormat_("(lastPathComponent ==[c] %@) AND (path MATCHES[c] %@)", theName, (".*?/" & theCompanyPrefix & ".*"))

Stefan. I tested that and it worked great. Thanks.

Hi peavine,

thank you very much again.
In my setup environment the script works just fantastically and it’s very fast. One file takes just about a second.

In the real environment I have some issues with speed and I guess it has something to do with network (NAS SMB sharing over LAN) or my adaption to the real world environment.
I had to interrupt one try after about 2 hours.

Then I cheated a little bit and narrowed the search to theSearchfolder (i.e.theCompanyPrefix-something) by changing the line:

set searchPath to “/Volumes/Store/Test/theCompanyPrefix-something/”

I’m not sure if that even made sense because I couldn’t figure out if your last version does a general search for the here_i_am file or if the search is already narrowed by the script to the folder theCompanyPrefix-something. Anyway with this cheat, it took about 5 minutes to do the search and copy the file successfully but the cheat won’t help me with the task in real life.

So I’m not sure, what to do next. It was possible, to even narrow the search one level down to the next subfolder which is named like ‘theCompanyPrefix-communication’.

Therefore I tried to unparse line with the NSPredicate but I didn’t succeed yet. I hope to find some developer documentation on the internet, that might help me with that. So I don’t give up an I’m still on it.

I’m really very thankful for your fantastic help so far and I humbly don’t want to bother you too much and steal your time.

Kind regards
Carl

Carl. If the script takes more than a minute or so to run then something is definitely wrong. Unfortunately, I’m not knowledgeable about servers and can’t even begin to suggest a solution in that regard.

One option that would be easy to implement is to limit the search path based on the company prefix, and this would go after the company name and prefix are parsed. For example,

if companyPrefix = "abcd" then
	set searchPath to "/Volumes/Store/Test/abcd-something"
else if companyPrefix = "xxxx" then
	set searchPath to "Volumes/Store/Test/xxxx-something"
end if

If I understand correctly, this will not work in your situation.

BTW, when searching for specified disk items, ASObjC first gets all files/folders in the search folder and then filters those items using specified rules (see code segment below). All of my suggested scripts work that way, and this is normally quite quick. Perhaps another forum member will suggest a different approach.

-- get all files/folders in theFolder but skip hidden files and the contents of packages
set folderContents to (fileManager's enumeratorAtURL:theFolder includingPropertiesForKeys:{} options:((current application's NSDirectoryEnumerationSkipsPackageDescendants) + (current application's NSDirectoryEnumerationSkipsHiddenFiles as integer)) errorHandler:(missing value))'s allObjects()

-- set the filter rules which in this case are 1) a specific file name, and
-- the file name''s path contains theCompanyPrefix
set thePred to current application's NSPredicate's predicateWithFormat_("(lastPathComponent ==[c] %@) AND (path MATCHES[c] %@)", theName, (".*?/" & theCompanyPrefix & ".*"))

-- apply the filter to the found files
set theFiles to (folderContents's filteredArrayUsingPredicate:thePred)

Thank you for the quick response Peavine,

Yes you’re right… in my situation narrowing the search path doesn’t work, because…

A) There are too many companyPrefixes to take them into account in the script,
B) the ‘something’ in the folder name can be any expression and therefore cannot be defined in the Script.

Thank you very much for the quick insight into ASObjC…
For me as a beginner it seems to be quite sophisticated, but I also find it interesting and so I’ll try to experiment a little bit with it.

Hi Stefan,

thank you very much for your help. Right now I’m trying to figure out how to adapt the script even more to my real world environment.

Maybe you can give me a little hint where I can find some information about the expressions in that lines below. Despite Google I haven’t found a way to unparse the expressions.

Thanks again,
Carl

• .* means 0 or more (any) characters, the period represents “any character” the asterisk represents “0 or more”
• ? The previous expression is optional. In this case it’s redundant and can be omitted
• / a slash
• the prefix

Here is a Quick Reference: https://koenig-media.raywenderlich.com/downloads/RW-NSRegularExpression-Cheatsheet.pdf

Thank you again. Very helpful. The PDF is great.

I haven’t found anything about…
==[c] %@)
Is this also part of the NSRegularExpression set?

Cheers
Carl

No, it’s part of the NSPrediate syntax, [c] means case insensitive and %@ is a placeholder for the first parameter after the comma, theName

For those, who like plain AppleScript solution:


-- file to search
property fileName : "Tless0.tiff"
-- file to copy
set fileToCopy to "HARD_DISK:Users:123:Downloads:TEST files:Abstract Background.ai"
-- were to search
set chosenFolder to (path to downloads folder) as text

-- coerce HFS path to System Events folder reference
tell application "System Events" to set chosenFolder to folder chosenFolder

-- maybe, the file doesn't exist at all. So, we use here TRY block
try
	set parentFolder to my recurse(chosenFolder)
	tell application "Finder" to duplicate file fileToCopy to folder parentFolder
end try

-- recursive handler
on recurse(chosenFolder)
	tell application "System Events"
		if (fileName is in (name of files of chosenFolder)) then
			return path of chosenFolder -- local exit from handler
		else
			repeat with subFolder in (get folders of chosenFolder)
				my recurse(subFolder)
				try
					return result -- global exit from handler
				end try
			end repeat
		end if
	end tell
end recurse

Very interesting, thank you for the help.
I made some progress by studying the Predicate Programming Guide by Apple.

But in the samples there the ‘predicateWithFormat’ expression is always followed by ‘:@’, like ‘predicateWithFormat:@’

Whereas in the given line…

set thePred to current application’s NSPredicate’s predicateWithFormat_(“(lastPathComponent ==[c] %@) AND (path MATCHES[c] %@)”, theName, (“.?/" & theCompanyPrefix & ".”))

the expression ist follwoed by ‘(’ like 'predicateWithFormat(’

Can you help me once more?

Cheers
Carl

«underscore-parentheses» and «colon» are synonyms, the former is legacy syntax, the latter is more convenient (and ObjC-compatible), however sometimes with complex expressions the former is still useful.

Thank you Stefan.

I experimented a little bit.
I tried to use the techniques to firstly search identify a folder with the syntax ‘companyPrefix-anything’. The folder is located directly inside another given folder.

Therefore I wanted to make sure, that the search is not extended to the subdirectories of the given folder. I included the term

  • (current application’s NSDirectoryEnumerationSkipsSubdirectoryDescendants)

in the following line.

set folderContents to (fileManager’s enumeratorAtURL:theFolder includingPropertiesForKeys:{} options:((current application’s NSDirectoryEnumerationSkipsPackageDescendants) + (current application’s NSDirectoryEnumerationSkipsSubdirectoryDescendants) + (current application’s NSDirectoryEnumerationSkipsHiddenFiles as integer)) errorHandler:(missing value))'s allObjects()

But I get an error message:

error “NSDirectoryEnumerationSkipsSubdirectoryDescendants kann nicht in Typ number umgewandelt werden.” number -1700 from NSDirectoryEnumerationSkipsSubdirectoryDescendants to number

English translation of the error message:

error “NSDirectoryEnumerationSkipsSubdirectoryDescendants can not be transformed to the type of a number.” number -1700 from NSDirectoryEnumerationSkipsSubdirectoryDescendants to number

Can you find my mistake?

Thanks for any help
Carl

NSDirectoryEnumerationOptions is an enum, actually an option set. In ObjC the options are compounded with the Bitwise OR Operator (|).

This particular operator is not available in AppleScriptObjC. The workaround is to add the integer values.

Either coerce each option to integer to be able to add them or simply specify literal 7 (4 + 2 + 1) in the options parameter.

Übrigens verstehe ich die deutschen Fehlermeldungen (by the way I understand the German error messages) :wink:

Great - danke sehr - it works - just added ‘as integer’ to the term:

  • (current application’s NSDirectoryEnumerationSkipsSubdirectoryDescendants as integer)

Dachte mir schon, dass ich es für Dich nicht übersetzen muss, aber vielleicht will ja jemand mitlesen.

FWIW, what you posted should work; the fact it doesn’t is a bug.

This is not a bug, but how AppleScript guesses the desired type in the result (what to get). With arithmetic operations, it considers an integer to be expected when the expression contains only an explicit integer (or explicit integers). If there is at least one real operand, the result is also real.


use scripting additions
use framework "Foundation"

property NSDirectoryEnumerationSkipsPackageDescendants : a reference to NSDirectoryEnumerationSkipsPackageDescendants of current application
property NSDirectoryEnumerationSkipsSubdirectoryDescendants : a reference to NSDirectoryEnumerationSkipsSubdirectoryDescendants of current application
property NSDirectoryEnumerationSkipsHiddenFiles : a reference to NSDirectoryEnumerationSkipsHiddenFiles of current application

(get NSDirectoryEnumerationSkipsPackageDescendants + 0) + (get NSDirectoryEnumerationSkipsSubdirectoryDescendants + 0) + (get NSDirectoryEnumerationSkipsHiddenFiles + 0)
--> 7
(get NSDirectoryEnumerationSkipsPackageDescendants + 0.0) + (get NSDirectoryEnumerationSkipsSubdirectoryDescendants + 0) + (get NSDirectoryEnumerationSkipsHiddenFiles + 0)
-- 7.0

In the case of the OP’s code, everything should go smoothly if you tell AppleScript to begin calculations from the end. Pay attention to the brackets. They 1) implicitly imply get keyword 2) change the order of adding:


use scripting additions
use framework "Foundation"

property NSDirectoryEnumerationSkipsPackageDescendants : a reference to NSDirectoryEnumerationSkipsPackageDescendants of current application
property NSDirectoryEnumerationSkipsSubdirectoryDescendants : a reference to NSDirectoryEnumerationSkipsSubdirectoryDescendants of current application
property NSDirectoryEnumerationSkipsHiddenFiles : a reference to NSDirectoryEnumerationSkipsHiddenFiles of current application

(NSDirectoryEnumerationSkipsPackageDescendants + ((NSDirectoryEnumerationSkipsSubdirectoryDescendants) + (NSDirectoryEnumerationSkipsHiddenFiles as integer)))
--> 7