Matching file names to files

Hi,
I’ve been battling with some code for the past two days.
I have a folder filled with files. I want to present the user with the option to chose files that begin with “2013” from within the folder.
I can successfully create a list of all the files in the folder.
I can successfully create a list of the names of all the files in the folder that begin with “2013”.
I can successfully get the user’s choices from said list of names.
What I can do is match the names to the files.
Equating them results in a false result even though the texts being compared seem to be identical. Any Ideas?


on run
	set _path to choose folder
	getFiles(_path)
end run

on getFiles(_path) --path correctly directs to a specific folder
	tell application "Finder"
		set _files to files of _path whose name starts with "2013" --sets _files to a list of some of the files in the folder _path
		set _fileNames to {} --defines _fileNames as an empty list
		set _chosenFiles to {} --defines _chosenFiles as an empty list
		repeat with _file in _files
			set end of _fileNames to name of _file
		end repeat
		--_fileNames is now a list of all the names of the files in the _files
		set _chosenFileNames to choose from list _fileNames with multiple selections allowed
		--_chosenFileNames is now a list of the selected names of files
		repeat with _chosenFileName in _chosenFileNames
			repeat with _file in _files
				if _chosenFileName is name of _file then set end of _chosenFiles to _file
				--for some reason _chosenFileName never equals (name of _file)
			end repeat
		end repeat
		--chosenFiles should now be a list of files (not names) whose names were chosen
	end tell
end getFiles

Browser: Safari 537.36
Operating System: Mac OS X (10.8)

If I understand well, the path to every selected files is :

(_path as text) & fileName
or
(_path as text) & “:” & fileName

Test once if (_path as text) ends with “:” to decide which formula must be used.

KOENIG Yvan (VALLAURIS, France) mardi 9 juillet 2013 16:45:17

Oops I forgot that _path is grabbed by choose folder.
Taking care of that we know that it ends with “:”

Here is an edited version doing the trick.


on run
	set _path to choose folder
	# _path as text ends with ":"
	getFiles(_path)
	result
end run

on getFiles(_path) --path correctly directs to a specific folder
	tell application "Finder"
		set _fileNames to name of files of _path whose name starts with "2013" --sets _files to a list of some of the files in the folder _path
		set _chosenFiles to {} --defines _chosenFiles as an empty list
		--_fileNames is now a list of all the names of the files in the _files
		set _chosenFileNames to choose from list _fileNames with multiple selections allowed
		--_chosenFileNames is now a list of the selected names of files
		repeat with _chosenFileName in _chosenFileNames
			#	set end of _chosenFiles to  (_path as text) & _chosenFileName
			# set end of _chosenFiles to file ((_path as text) & _chosenFileName)
			# set end of _chosenFiles to alias ((_path as text) & _chosenFileName)
			set end of _chosenFiles to file _chosenFileName of _path
		end repeat
		--chosenFiles should now be a list of files (not names) whose names were chosen
	end tell
	return _chosenFiles
end getFiles


Hi,

this script retrieves only the file names and later get the file references with the whose filter


on getFiles(_path) --path correctly directs to a specific folder
	tell application "Finder"
		set _fileNames to name of files of _path whose name starts with "2013" -- sets _files to a list of some of the files in the folder _path
	end tell
	set _chosenFiles to {} --defines _chosenFiles as an empty list
	--_fileNames is now a list of all the names of the files in the _files
	set _chosenFileNames to choose from list _fileNames with multiple selections allowed
	--_chosenFileNames is now a list of the selected names of files
	if _chosenFileNames is false then return
	repeat with _chosenFileName in _chosenFileNames
		tell application "Finder" to set end of _chosenFiles to file _chosenFileName of _path
	end repeat
	return _chosenFiles
	--chosenFiles should now be a list of files (not names) whose names were chosen
	
end getFiles

Hi,

It seems to me that it would be better to get the references first. Then you can get the names through the references. You can’t get the references directly from the names. Just thinking about avoiding calls to Finder with the every element reference form. Am I thinking wrongly?

Thanks,
kel

Hi kel.

From the point of view of speed ” especially if there are a lot of files ” it’s better for the Finder to return all the names at once (in a single command) than for it to put together and return references and then have to extract the name from each reference in turn in a repeat.

Faster still would be to get the names of all the files in the folder, without using a ‘whose’ filter, and then use vanilla AppleScript in a repeat to parse the list for names beginning with “2013”.

As you can see from Yvan’s script (in the edit which exists as I post this), you can make a reference from a name by placing ‘file’ and the name and ‘of’ in front of a reference to the folder.

Hi Nigel,

:cool: That’s so logical. I mean you already have the container and all you need is the name.

Thanks a lot,
kel

Thanks for the replies everyone, very informative!
I changed my code to include your suggestions and also avoided having my Finder run a “whose” command through the _path folder, as it was slowing the script significantly (a few thousand files…)

I still have an issue with opening files from the list. I’d appreciate your input.


on getFiles(_path)
	tell application "Finder" to set _fileNames to name of files of _path -- 
	set _matchingNames to {} --defines _matchingNames as an empty list
	repeat with _fileName in _fileNames
		if _fileName starts with "2013" then set end of _matchingNames to _fileName
	end repeat
	set _chosenFiles to {} --defines _chosenFiles as an empty list
	set _chosenFileNames to choose from list _matchingNames with prompt "Which file(s) would you like to open?" with title "Choose Files" OK button name "Open File(s)" with multiple selections allowed --_chosenFileNames is now a list of the selected names of files
	if _chosenFileNames is false then return
	repeat with _chosenFileName in _chosenFileNames
		tell application "Finder" to set end of _chosenFiles to (1st file of _path whose name is _chosenFileName)
		--tell application "Finder" to open (1st file of _path whose name is _chosenFileName) --running this line opens the files successfully, but I rather have them as a list that I can access at will.
	end repeat
	
	repeat with _chosenFile in _chosenFiles
		tell application "Finder" to open file _chosenFile --results in an error: Can't make document [file] into type integer.
	end repeat
	return _chosenFiles
end getFiles

I’d open and save the file reference in the same repeat loop


on getFiles(_path)
	tell application "Finder" to set _fileNames to name of files of _path -- 
	set _matchingNames to {} --defines _matchingNames as an empty list
	repeat with _fileName in _fileNames
		if _fileName starts with "2013" then set end of _matchingNames to _fileName
	end repeat
	set _chosenFiles to {} --defines _chosenFiles as an empty list
	set _chosenFileNames to choose from list _matchingNames with prompt "Which file(s) would you like to open?" with title "Choose Files" OK button name "Open File(s)" with multiple selections allowed --_chosenFileNames is now a list of the selected names of files
	if _chosenFileNames is false then return
	repeat with _chosenFileName in _chosenFileNames
		tell application "Finder"
			set _chosenFile to file _chosenFileName of _path
			open _chosenFile
		end tell
		set end of _chosenFiles to _chosenFile
	end repeat
	return _chosenFiles
end getFiles

Hi Stefan,
Thanks for the quick reply. as you can see in my latest code, I already had the option you mentioned.
I can open the files directly from the loop (as you did), but I want to save them as a list and them open them at a later point in my code.
The last repeat loop in my code throws an erro when I try and open files from the _chosenFiles list. I probably need to save the items into the list as aliases or some other type of data, but I can’t seem to get it to work.

Hello.

This is more in line with Nigel Garvey’s solution, in addition, I do as Christopher Stone, and creates an alias list, as that is the fastest, for further processing.

I use some handlers here, one new: I quicksort an alias list. the result is a list of aliases that can be processed further. The _filter handler, filters a list, it takes a handler as a parameter, this time, the handler a match is adjusted to coerce an alias into text, -as the quicksort handler. I tested it on a folder with 397 files, 99 for 2013, and it used 7.82 seconds.

The caveat is that it can’t be any 2013 in folder names when you use this.

Further processing of the list of aliases should be straight forward.

Further optimization could be to coerce every item of the list into a text

global searchTerm
set searchTerm to "2013"
# holds the searchTerm, to communicate it to the filterer handler.

tell application "Finder"
	set file_list to every file of folder (choose folder) as alias list
end tell
set listCount to length of file_list

quicksortAliasList(file_list, 1, listCount)
set matchesList to _filter(file_list, amatch)

log matchesList

-- http://macscripter.net/viewtopic.php?id=38978
on quicksortAliasList(theList, theLeft, theRight)
	set i to theLeft
	set j to theRight
	set v to item ((theLeft + theRight) div 2) of theList as text -- pivot
	repeat while (j > i)
		repeat while (item i of theList as text < v)
			set i to i + 1
		end repeat
		repeat while (item j of theList as text > v)
			set j to j - 1
		end repeat
		if (not i > j) then
			tell theList to set {item i, item j} to {item j, item i} -- swap
			set i to i + 1
			set j to j - 1
		end if
	end repeat
	if (theLeft < j) then quicksortAliasList(theList, theLeft, j)
	if (theRight > i) then quicksortAliasList(theList, i, theRight)
end quicksortAliasList


on amatch(x)
	global searchTerm
	if searchTerm is in (contents of x as text) then return true
	return false
end amatch

on _filter(l, crit)
	-- © Matt Neuburg AppleScript The Definitive Guide Second Edition.
	script filterer
		property criterion : crit
		on _filter(l)
			if l = {} then return l
			if criterion(item 1 of l) then
				return {item 1 of l} & _filter(rest of l)
			else
				return _filter(rest of l)
			end if
		end _filter
	end script
	return filterer's _filter(l)
end _filter

probably the double reference causes the error
_chosenFile is already a Finder file reference


tell application "Finder" to open _chosenFile

Thanks Stefan and everyone else My script is now running smoothly!

I rewrote my proposal to drop Finder and speak to System Events.

Here it’s dramatically faster.


alias "Macintosh HD:Users:yvankoenig:Documents:tempo:"

getFiles(result)
result

on getFiles(_path) --path correctly directs to a specific folder
	tell application "System Events"
		set _fileNames to name of files of _path whose name starts with "2013"
	end tell
	set _chosenFiles to {} --defines _chosenFiles as an empty list
	--_fileNames is now a list of all the names of the files in the _files
	set _chosenFileNames to choose from list _fileNames with multiple selections allowed
	--_chosenFileNames is now a list of the selected names of files
	tell application "System Events"
		repeat with _chosenFileName in _chosenFileNames
			#	set end of _chosenFiles to  (_path as text) & _chosenFileName
			# set end of _chosenFiles to file ((_path as text) & _chosenFileName)
			# set end of _chosenFiles to alias ((_path as text) & _chosenFileName)
			set end of _chosenFiles to file _chosenFileName of _path
		end repeat
	end tell
	--chosenFiles should now be a list of files (not names) whose names were chosen
	
	return _chosenFiles
end getFiles

KOENIG Yvan (VALLAURIS, France) mercredi 10 juillet 2013 16:53:56

Hello.

It is hard to write a handler that returns a range of items in a sorted list, with binary properties, with regards to speed. It just occured to me, that I only have to find the first item, and then go for a convention of retesting the item. If I can do that, then the solution will take around a second on my machine, in comparison to my former result of 7.82 secs. :slight_smile:

Edit

In order to write something like bsearch, that returns a range, I’ll have to coerce the alias list to a text list, which is done in a whiff, and having done that, then I may as well use sed/grep to get the ranges of names, so I don’t even bother to post it.

for

a shell call might be the fastest way (but not tested)


set _fileNames to paragraphs of (do shell script "ls  " & quoted form of POSIX path of _path & " | grep ^2013")

a side-effect is a sorted list

Hello.

Here is a version with grep, that seem to work fast enough ( I have tested it with 800 files. )


script o
	property l : {}
	property m : {}
	property n : {}
end script

tell application "Finder"
	set file_list to every file of folder (choose folder) as alias list
end tell
set listCount to length of file_list
set {o's l, i} to {file_list, 1}
repeat listCount times
	set end of o's m to item i of o's l as text
	set i to i + 1
end repeat

set {tids, AppleScript's text item delimiters} to {AppleScript's text item delimiters, linefeed}
set theText to o's l as text
set AppleScript's text item delimiters to tids

set o's m to paragraphs of (do shell script "grep '2013[^:]*$' <<<" & quoted form of theText & "sort ")

set o's n to paragraphs of (do shell script "grep -o '2013[^:]*$' <<<" & quoted form of theText & "sort ")
set {listCount, o's l, i} to {count o's m, {}, 1}
repeat listCount times
	set end of o's l to item i of o's m as alias
	set i to i + 1
end repeat
tell current application
	activate
	set selectedFiles to (choose from list o's n with multiple selections allowed)
end tell
if selectedFiles is false then error number -128

set idlist to {}

# tell application "Finder" or "System Events"
repeat with aFile in selectedFiles
	set end of idlist to indexOfItem(aFile, o's n)
	# or do an op with item (indexOfItem(aFile, o's n) o's l here..
end repeat
# end tell
on indexOfItem(theItem, theList) -- Emmanuel Levy

		set text item delimiters to return
		set theList to return & theList & return
		set text item delimiters to {""}
		try
			-1 + (count (paragraphs of (text 1 thru (offset of (return & theItem & return) in theList) of theList)))
		on error
			0
		end try
end indexOfItem

indexOfItem is overkill, with given folder path and file name you can create the alias reference very simple


set baseFolderPath to (choose folder) as text
tell application "Finder"
	set file_list to every file of folder baseFolderPath -- as alias list is not needed, Finder specifier can be coerced to text without a Finder tell block
end tell
-- .
repeat with aFile in selectedFiles
	set end of idlist to alias (baseFolderPath & aFile)
end repeat