Hello there,
I would like to run a certain macro only if I have a single object selected in Finder. I know I can use the “For Each Item in the Collection” action to act on the path, but how do I make sure, that only a single path is selected? Is there some action that returns the Finder’s selection length? Or do I need to use another “For Each Item in the Collection” action to count the items first?
You really can't do better than what JM has shown you as a test.
However – since you're calling AppleScript anyway, it's a good idea to do as much as you can within the AppleScript. It'll save execution time.
Let's say I just want to return the POSIX Path of the selected item to Keyboard Maestro:
--------------------------------------------------------------
tell application "Finder"
set finderSelectionList to selection as alias list
if length of finderSelectionList = 0 then
error "No files were selected in the Finder!"
else if length of finderSelectionList = 1 then
set itemPosixPath to POSIX path of item 1 of finderSelectionList
return itemPosixPath
else if length of finderSelectionList > 1 then
error "Too many files were selected in the Finder!"
end if
end tell
--------------------------------------------------------------
Wow, thank you so much everybody. I went with Michael’s solution, as it was the fastest one that worked for me. Chris, I tried to modify your Applescript in a way that would work without errors (i.e. return no result, if the fileSelectionList’s length was not 1), but did not get this to work, it still returned a file path, even if I had more than one files selected. This is the non-working code I had:
--------------------------------------------------------------
tell application "Finder"
set finderSelectionList to selection as alias list
if length of finderSelectionList = 1 then
set itemPosixPath to POSIX path of item 1 of finderSelectionList
return itemPosixPath
end if
end tell
--------------------------------------------------------------
But no worries, Michael’s suggestion worked really well and noticeably faster than my for each counting macro solution that I had in place earlier.
Thanks again!
Edit: And if somebody could give me a hint how I can do Applescript syntax highlighting in my posts, then I could add this to my posts in the future …
Glad to hear that it worked for you.
I like it because it is simple and easy to use, and doesn't require the end-user to know anything about AppleScript. Anytime you have to change a script, you run the risk of introducing an error. No changes needed here.
###How to Put AppleScript in a Forum Code Block
Just insert your script between the lines with the triple backquotes:
```applescript
-- Your Script Here
Here is a macro that will paste the script on the clipboard into the forum in the proper format:
####[MACRO: KM Forum -- Paste Script Block](https://forum.keyboardmaestro.com/t/paste-script-block-in-km-forum/4047)
That's odd, because that code is perfect for what it is. There must be a little glitch in how you're using it.
Are you perhaps outputting a Keyboard Maestro variable that was already set?
--------------------------------------------------------------
tell application "Finder"
set finderSelectionList to selection as alias list
if length of finderSelectionList = 1 then
set itemPosixPath to POSIX path of item 1 of finderSelectionList
return itemPosixPath
else
return "FALSE"
end if
end tell
--------------------------------------------------------------
Keep in mind it's dangerous to assume a variable will behave as you want it to — it's a good idea to very specifically set it to something (or delete it).
Also — be careful about asking and assuming — when practical do your own testing, so you know from practice what an action does.
That said — don't bang your head against the wall very long before starting to ask questions.
Ok, I was not returning FALSE I was actually not returning anything. Is Applescript returning some empty value then, instead? I just assumed it would leave my originally set variable alone, if I don't return anything explicitly.
But now, if I return FALSE in the else statement and check for that instead of 0, then it does work.
Thanks again, very impressed by how much the pros are taking care of the newbies here, I learned a lot already.
Thanks for the JXA script, Rob. It's always great to see both AppleScript and JXA for the same function.
One question: What would be the JXA equivalent of this very efficient, fast AppleScript:
tell application "Finder"
return (count of (selection as alias list))
end tell
The part I'm particularly interested in is the "selection as alias list".
We have found this is much faster than getting a selection of Finder objects.
Thanks, but that is not exactly what I'm looking for.
Forget about length/count for the moment.
According to the Finder SDEF:
AliasListObject : A list of aliases. Use ‘as alias list’ when a list of aliases is needed (instead of a list of file system item references).
However, I cannot figure out how to use "AliasList".
Here's my test script, where I have unsuccessfully tried various syntax:
'use strict';
(function run() { // this will auto-run when script is executed
/*
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
FROM Finder SDEF:
AliasList Object : A list of aliases. Use ‘as alias list’ when a list of aliases is needed (instead of a list of file system item references).
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
*/
var finderApp = Application('Finder')
var itemList = finderApp.selection() // returns array of Finder objects.
var item1 = itemList[0];
// doesn't work as expected. Results same as without "as alias list".
var itemAliasList = finderApp.selection("as alias list");
/*
I have also tried, without success:
var itemList = finderApp.selection().AliasList
var itemList = finderApp.selection().AliasList()
var itemList = finderApp.AliasList()
*/
var alias1 = itemAliasList[0]
debugger; // doesn't really help.
return alias1;
} // END of function run()
)();
My first thought is that the unmodified .selection() seems good enough, (takes a second or two here with 400+ selected files, and its hard to think of an activity more trivial and marginal than scripting anyway – probably better to optimise hours of human time than seconds or less of machine time).
My second, that if you are sure that you really want to dive down that rabbit hole, options to constructors are sometimes expected in the form of a dictionary with a key-value pair, so that avenue might conceivably be worth experimenting with (though the probabilities look almost as slim as the rewards).
But, remember that there is a single SDEF file and an automatic conversion for the JS presentation of it. There are marginal cases where material embedded in the world of AS datatypes doesn’t really correspond to JS data types, and the explanatory strings are not always very thoroughly edited for language-indenpendence.
(The Array returned by .selection() is of references rather than property-laden objects anyway …)
tell application "Finder"
set finderSelectionList to selection as alias list
if length of finderSelectionList = 1 then
set itemPosixPath to POSIX path of item 1 of finderSelectionList
return itemPosixPath
end if
end tell
In this script you ARE explicitly returning something in each case, you just don't know it.
If there is NO Finder-selection then you're returning an empty AppleScript-list-object, and Keyboard Maestro is translating that to "" (or zilch).
Use the variable-inspector in the Keyboard Maestro preferences to discover when you're putting in them.
Possibly ... tho is it worth our taking the time to find out ?
Best, I think, to just reach for the tool that matches the task – looks like it may well be AppleScript in this case. (Though JS is typically the better instrument, often by more than an order of magnitude, where sheer processing speed is genuinely required)
(One can even inline one language inside another – I have occasionally done that, in both directions ...
(() => {
'use strict';
const
a = Application.currentApplication(),
sa = (a.includeStandardAdditions = true, a);
// evalAS :: String -> IO String
const evalAS = s =>
sa.doShellScript(
['osascript -l AppleScript <<OSA_END 2>/dev/null']
.concat([s])
.concat('OSA_END')
.join('\n')
);
return evalAS(
'tell application "Finder" to count of (selection as alias list)'
);
})();
On the other hand, by the time that we are tempting fate with manual GUI selection of hundreds of files, I wonder whether script speed is really the issue that most needs attention ?
-- JAVASCRIPT FROM APPLESCRIPT ------------------------------------------------
-- evalJS :: String -> IO String
on evalJS(s)
do shell script ¬
unlines({"osascript -l JavaScript <<JXA_END 2>/dev/null"} & {s} & {"JXA_END"})
end evalJS
-- GENERIC FUNCTIONS ----------------------------------------------------------
-- unlines :: [String] -> String
on unlines(xs)
intercalate(linefeed, xs)
end unlines
-- intercalate :: Text -> [Text] -> Text
on intercalate(strText, lstText)
set {dlm, my text item delimiters} to {my text item delimiters, strText}
set strJoined to lstText as text
set my text item delimiters to dlm
return strJoined
end intercalate
-- TEST -----------------------------------------------------------------------
evalJS("encodeURI('" & POSIX path of (path to application support) & "')")