Count selected objects in Finder

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?

Thank you!

trych

You could do something like this:

This simple AppleScript should do the job for you.
It is very fast.

tell application "Finder"
  return (count of (selection as alias list))
end tell

1 Like

Hey @trych,

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
--------------------------------------------------------------

Of course much more sophistication is possible...


###Here's how you might use that in a macro:
``
Finder ⇢ Operating on only 1 selected item.kmmacros (6.4 KB)

###Here's a little different take than Christian's macro:
``
Finder ⇢ Operate on only 1 selected item.kmmacros (5.1 KB)

Personally I would never do this. I'd use AppleScript. But it takes all kinds...  :sunglasses:

-Chris

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)

Hey @trych,

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.

We don't need no dain-brammage...  :sunglasses:

-Chris

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.

Here is a screenshot of what I had (not working):

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.

trych

FWIW a JavaScript for Automation translation of the core script:

(function () {
    'use strict';

    return Application('Finder')
        .selection()
        .map(function (x) {
            return decodeURI(x.url())
                .slice(7); // Dropping 'file://'
        })
        .join('\n');       // Array of urls -> String of lines
})();

Or a little more cleanly in Sierra, which supports ES6 JavaScript:

(() => Application('Finder')
    .selection()
    .map(x =>
        decodeURI(x.url())
        .slice(7)) // Dropping 'file://'
    .join('\n')    // Array of urls -> string of lines
)();

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.

Application('Finder').selection().length

Thanks, I wish I could JavaScript, which I am much more efficient at, but I am still on OS X 10.8.5, so I’ll have to fight with AppleScript. :pensive:

Thanks, but that is not exactly what I'm looking for.
Forget about length/count for the moment.

According to the 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).

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()
)();

Your thoughts?

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 …)

OK, I understand the general concept, but I don't have a clue as to how to express that with "AliasList" and "selection()."

You have a lot more experience here, so I'd appreciate a guess on your part.

Thanks.

Hey JM,

I suspect you can't. I believe JavaScript doesn't grok AppleScript alias objects.

Rob's script takes 12 seconds to process 1000 files on my system.

By contrast the equivalent AppleScript with as alias list takes 1.5 seconds.

Rob – is it possible to use an as text coercion? This also works very fast in vanilla AppleScript.

-Chris

Hey @trych,

You know what they say about assuming...  :sunglasses:

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.

-Chris

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 ... :slight_smile:

(() => {
    '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 ?

or, of course, for symmetry, sth like:

-- 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) & "')")

Ok, so even if there is no return statement, it always will return something. Got it. Thanks!