If I’m understanding the problem correctly I believe what you were attempting can be done. It’s just that the filter form is very slow.
set srcFldr to ((path to desktop as text) & "Pictures:")
set destFldr to alias ((path to home folder as text) & "test_directory:TestFldr_Dest:")
set searchStrings to {"Berlin", "City", "Airport", "Restaurant"}
set AppleScript's text item delimiters to "\" and name contains \""
set _query to "tell application \"Finder\"
(files of alias \"" & srcFldr & "\" whose name contains \"" & (searchStrings as text) & "\") as alias list
end tell"
set fileList to run script _query
tell application "Finder"
duplicate fileList to destFldr
end tell
I’ve left off the duplicate in the Finder code, since that’s been well covered already.
The result is a list of aliases.
set searchStrings to {"Berlin", "City", "Airport", "Restaurant"}
set srcFldrPath to POSIX path of ((path to desktop as text) & "Pictures:")
set destFldr to alias ((path to home folder as text) & "test_directory:TestFldr_Dest:")
set AppleScript's text item delimiters to linefeed
set shCMD to "sed -En '/" & item 1 of searchStrings & "/"
set endOfCmd to {}
set searchStrings to rest of searchStrings
repeat with i in searchStrings
set end of endOfCmd to "}"
set shCMD to shCMD & "{" & linefeed & "/" & i & "/"
end repeat
set shCMD to shCMD & "p"
set endOfCmd to endOfCmd as text
set shCMD to shCMD & linefeed & endOfCmd & "'"
set shCMD to "srcDIR='" & srcFldrPath & "';
ls -1 \"$srcDIR\" | " & shCMD & " \\
| sed -E \"s!^!${srcDIR}!\""
set fileList to paragraphs of (do shell script shCMD)
repeat with i in fileList
set contents of i to alias POSIX file (contents of i)
end repeat
fileList
. or indeed can be dispensed with altogether if ls returns the paths too:
set searchStrings to {"Berlin", "City", "Airport", "Restaurant"}
set srcFldrPath to POSIX path of ((path to desktop as text) & "Pictures:")
set destFldr to alias ((path to home folder as text) & "test_directory:TestFldr_Dest:")
set AppleScript's text item delimiters to linefeed
set shCMD to "sed -En '/" & item 1 of searchStrings & "/"
set endOfCmd to {}
set searchStrings to rest of searchStrings
repeat with i in searchStrings
set end of endOfCmd to "}"
set shCMD to shCMD & "{" & linefeed & "/" & i & "/"
end repeat
set shCMD to shCMD & "p"
set endOfCmd to endOfCmd as text
set shCMD to shCMD & linefeed & endOfCmd & "'"
set shCMD to "ls " & quoted form of srcFldrPath & "* | " & shCMD -- NB. the asterisk.
set fileList to paragraphs of (do shell script shCMD)
repeat with i in fileList
set contents of i to alias POSIX file (contents of i)
end repeat
fileList
The second sed command in the shell script can in fact be incorporated into the first:
set searchStrings to {"Berlin", "City", "Airport", "Restaurant"}
set srcFldrPath to POSIX path of ((path to desktop as text) & "Pictures:")
set destFldr to alias ((path to home folder as text) & "test_directory:TestFldr_Dest:")
set AppleScript's text item delimiters to linefeed
set shCMD to "sed -En '/" & item 1 of searchStrings & "/"
set endOfCmd to {}
set searchStrings to rest of searchStrings
repeat with i in searchStrings
set end of endOfCmd to "}"
set shCMD to shCMD & "{" & linefeed & "/" & i & "/"
end repeat
set shCMD to shCMD & "s!^!'${srcDIR}'!p"
set endOfCmd to endOfCmd as text
set shCMD to shCMD & linefeed & endOfCmd & "'"
set shCMD to "srcDIR='" & srcFldrPath & "';
ls -1 \"$srcDIR\" | " & shCMD
set fileList to paragraphs of (do shell script shCMD)
repeat with i in fileList
set contents of i to alias POSIX file (contents of i)
end repeat
fileList
Here’s another version. Filtering in Cocoa is done using things call predicates, which are similar to whose clauses. In fact, Cocoa scripting’s whose clauses are built on predicates.
But there are also things called compound predicates: you create a bunch of predicates, make a compound predicate from them, and then filter with that. It seems to me ideal for this sort of problem.
So this version requires Yosemite (or you can put it in a script library under Mavericks):
use AppleScript version "2.4"
use scripting additions
use framework "Foundation"
set searchStrings to {"Berlin", "City", "Restaurant"}
set srcFldrPath to POSIX path of ((path to desktop as text) & "Pictures:")
-- we need it in NSString form later, so...
set srcFldrPath to current application's NSString's stringWithString:srcFldrPath
-- get list of files
set theNSFileManager to current application's NSFileManager's defaultManager()
set theFiles to theNSFileManager's contentsOfDirectoryAtPath:srcFldrPath |error|:(missing value)
-- build list of predicates from search strings
set thePreds to {}
repeat with aString in searchStrings
set end of thePreds to (current application's NSPredicate's predicateWithFormat_("self contains %@", aString))
end repeat
-- filter names using compound *and* predicate
set theFiles to theFiles's filteredArrayUsingPredicate:(current application's NSCompoundPredicate's andPredicateWithSubpredicates:thePreds)
-- convert names to full paths
set fileList to (srcFldrPath's stringsByAppendingPaths:theFiles) as list
-- convert to list of aliases
repeat with i in fileList
set contents of i to alias POSIX file (contents of i)
end repeat
I’d imagine timings will vary depending on the number of files in the directory and so on, but in my very simple test it takes around 0.001 seconds, compared with about 0.01 seconds for the sed versions. (I found Nigel’s a fraction slower than Chris’s.)
This is a reply to the post where you addressed me. What I meant Nigel’s script did so well, was that it tested that the search strings ended on the word boundaries, so for instance Hamburger wasn’t included when Hamburg was the search string (Exact Match). You get a very slow script if you want to avoid those cases while using filtering with Finder. The reasons for this is of course, that since you really can’t make any assumptions about where the individual searchstrings are located in a file name, you can’t end the strings with either a dot or an underscore. This means again that you’ll have to test each filename from the filtered set like Nigel did.
Nice bunch of script all you got here, I’ll have to work through them.
I must admit I didn’t compare their timings. It was after two in the morning here, I’d only been trying to economise on Chris’s code, and ” since my test folder only contains seven, carefully named files ” any differences in the time taken to process the text would be too close to be reliable and easily swamped in practice by variations in the times taken to duplicate the identified files ” which none of the later scripts actually bothers to do. It is interesting that you find Chris’s script faster though, because it runs the shell script twice! I’ll check it out again later on.
This would probably have to be dealt with by crossing off strings as they were found. But Marc hasn’t mentioned it as a possibility, so I wouldn’t be inclined to worry about it here. (Yet, at least!) When writing the script in post #10, I did think of eliminating the name extensions in case they coincided with any of the search terms; but in the end, I decided it was unlikely to happen.
Edit: Ah. I see the forum times are correct again. The clocks must have gone back in the US. So it was between one and two in the morning when I posted my modifications of Chris’s script.
Later: With the redundant ‘do shell script’ removed from Chris’s script, I can’t find any reliable difference in speed between it and my variations. Shane’s ASObjC script is of course eight to ten times as fast as any of them.
I couldn’t time Shane’s script at first, because the code I use to load my timer into a script .
set timer to (load script file ((path to scripts folder from user domain as text) & "Libraries:Timer.scpt"))
. kept returning the stupid error “Can’t make current application into type file.” The cure turned out to be:
set timer to (load script (get alias ((path to scripts folder from user domain as text) & "Libraries:Timer.scpt")))
If there was a folder with lots of files, then a do shell script with mdfind -onlyin, and a compiled search expression maybe beneficiary. I just mention that, as another option, maybe it is faster than the sed version of things, maybe not. I don’t have the number of files to test this anyway, so I leave it in the open.
That’s why I mentioned it – but the difference was slight, and my test folder very basic. I guess what I was really saying was that I was surprised there wasn’t a significant difference the other way around, though.
FWIW, changing the repeat loop slightly to use grep will deal with hamburgers and Berliners:
repeat with aString in searchStrings
set aString to (current application's NSRegularExpression's escapedPatternForString:aString) -- escape any significant characters
set end of thePreds to (current application's NSPredicate's predicateWithFormat_("self matches %@", "(?i).*" & aString & "[ _. ].+"))
end repeat
That should theoretically slow it down a smidge, but I can’t see any difference here.
Probably, but I’m always nervous about the risk of the Spotlight database being wrong when there’s not a lot time to be gained. I just changed my test folder from a handful of files to 500-odd, and the sed time only rose from 0.01 to 0.053 seconds (whereas the ASObjC time went to 0.034).
I wrote a small handler earlier, that can be used without well, it actually loads, but from a 2.3 style script library.
I haven’t made terminology available yet, since I try to compose libraries.
debug timing.scptd
use AppleScript version "2.3"
use scripting additions
use framework "Foundation"
use framework "AppKit"
on millisec()
tell current application's NSDate's alloc()'s init() to return ((its timeIntervalSince1970()) * 1000)
end millisec
Here is example usage:
use AppleScript version "2.3"
use scripting additions
use dbg : script "debug_timing"
set a to dbg's millisec()
set b to ((dbg's millisec()) - a)
log b
-- > 0.371826171875
I agree with you, you also have the risk of the spotlight index being corrupted, rendering the solution unusable.
Such a solution with mdfind would probably be better when the sought items are spread over several folders, or if other file attributes are forming the search criteria, (than the filenames).
Shane undoubtedly realized that line was an extraneous leftover from the development process and removed it. (I’ve since elided it from the original.)
I used a test folder with 500 files in it, and in my tests my script is consistently a trifle faster than your second effort when timed in Smile with chrono.
But we’re only talking about 2 or 3 thousands of a second.
The why of that is less than obvious, but the difference is so small I’m not going to futz with it anymore.
It’s probably worth adding that it’s per application. So if you are running scripts from FastScripts, for example, the delay will happen the first time the framework is loaded – from then on, there’ll be no delay when any scripts that use that framework are run from FastScripts.