How to get return value from an AppleScript saved as an app?

I have a macro that uses an Execute an AppleScript action to execute a script file. In a (very small) nutshell, the script:

  • reads a text file,
  • puts up a pick list using the file's contents with choose from list, and
  • if the script was canceled, executes return "", else
  • returns the chosen items to KM in a string, one item per paragraph, using return theItems.

KM receives the chosen items into a variable, massages the text depending on the app in the front when the macro was invoked, and puts the massaged text into that app.

Works fine, except that the pick list doesn't get input focus. It's a flow-breaker to be merrily keyboarding away and after hitting the hotkey to invoke the script, to have to find the mouse and fiddle around to click on the pick list before I can make my selections.

I've not been able to find any AppleScript workaround to get the pick list to get input focus.

So I saved the script as an app and used an Execute Shell Script action with the command open '/Users/Me/Path to/My App.app/'. Works fine, the pick list gets input focus and there's no need to mouse around. But, whereas the script returned the selected items just fine, the app never returns anything. Or maybe KM never gets the return value? Impossible to tell.

But, a display dialog theItems in the script/app just before return theItems shows that theItems has the desired value.

The attached script, screenshot of the macro, and macro, illustrate the problem.

  1. Copy the script into Script Editor, save it as Script, and save it as Application.
  2. Download the macro & import into KM.
  3. In the macro, select the paths to the script & app to reflect where you saved them.
  4. Run the macro and you'll see that:
  • both the script and the app report what they're returning,
  • KM receives the return values from the script, and
  • KM never receives anything from the app.

I'd really like to use this simple method of just using return theItems, and at the very least, understand what the issue is, before I resort to workarounds. Maybe I'm doing something stupid or not doing something obvious, that I'm just not seeing? Maybe Mac apps in general, or app-wrapped AppleScripts specifically, never return anything? Or something else?

use AppleScript version "2.4" -- Yosemite (10.10) or later
use scripting additions

set answer to choose from list {"item 1", "item 2", "item 3"} with multiple selections allowed

if answer = false then
	set returnValue to "canceled"
	
else
	set returnValue to ""
	repeat with anItem in answer
		set returnValue to returnValue & anItem & return
	end repeat
end if

display dialog "the script/app is returning:" & return & return & "\"" & returnValue & "\"" with title "Message from the script/app" buttons {"OK"} default button "OK"

return returnValue

Get return value from script or app.kmmacros (10.0 KB)

tell application "Keyboard Maestro Engine"
	setvariable "chosenListItem" to returnValue
end tell

I hate to ask a possibly stupid question, but if that's all the AppleScript is doing, couldn't you do the whole thing in Keyboard Maestro and skip the return steps?

Keyboard Maestro's Prompt with List action can read directly from a file:

image

Enable multi-selection in the Gear menu, and you'll have everything the AppleScript was giving you…or is there more to it than that?

-rob.

Thanks for your reply.

Yes, that is certainly one of the workarounds.

But I really want to understand why return returnValue actually returns the value when executed from a script, but nothing is returned when executed from the same script that is wrapped up in an app. For example: is this behavior "as designed", or a bug somewhere in the chain (KM, osascript, macOS,...)?

Thanks for your reply.

The example I included in my post is the smallest amount of code, and the smallest macro, that reproduce the problem. The actual script is over 700 lines of code, not including the script libraries that it uses, and does much much more than just put up a pick list. Doing everything the script does might be feasible in KM, but would be so convoluted that it would be difficult to create, test, understand, and maintain.

I sort of figured as much, but thought I'd ask as sometimes it really is just that simple :).

-rob.

Because a standalone app is not a sub-routine.

Quite apart from the fact that apps don't return values (their type is IO null) 700 lines is already approaching the scale (700-1000) where AppleScripts begin, in my experience, to flicker and fail with intermittent memory-management issues.

At c. 1000 you generally have to throw them away and start again in another language. Probably JS, if you need the osascript interfaces.

Thanks for your reply.

What "type" are you referring to? Can you point me to some resource that explains this further?

A programming language expression (e.g. an arithmetic expression, or a function call), has a return value of some type (String, Integer, Bool, Record etc etc) within the evaluation space of a programming language.

An application is NOT embedded in the evaluation space of any programming language. The macOS operating system may get a 0 or 1 from it (successful completion vs failure), and the app may cause IO updates to file systems, screens or other devices, but that's about it.

An application is not an expression, evaluating to a value of any type, within a programming language. It doesn't have a return value.

(If it did, to where or what would it return that value ? It may be built using AppleScript, but it is not being evaluated inside an AppleScript interpreter)


If you want to extract more from it than 0=success or 1=failure, then you probably need it to write something out the file-system.

The "application" in this case is not the script, or the app that contains the script, or a "programming language," and there is no context of something happening inside a programming language.

AppleScripts are not executable objects.

The "application" in this case is osascript, as in osascript /Path/To/foo.scpt or osascript /Path/To/foo.app/Contents/Resources/Scripts/main.scpt.

foo.scpt and main.scpt contain bytecode produced by osacompile. And (as far as I can tell), they are identical (except for their file name). Yet, they produce different results, depending on whether the scpt is wrapped in an .app package, or not: the unpackaged scpt returns the specified value, but the packaged version of the same script in an .app returns nothing.

As to:

['it' referring to an application in general] stdout or stderr would seem likely candidates. Assuming that osascript writes to one of those when executing return something.

Exploring this more, I find that /usr/bin/osascript -e 'return "foo"' (in Terminal) produces foo, written to stdout (to Terminal).

As does cat /Users/me/_Dev/AppleScripts/return\ foo.txt | /usr/bin/osascript, where foo.txt contains the single line of text, return "foo".

These demonstrate that 1) osascript is indeed capable of returning return values (which of course is already evident given that executing osascript /Path/To/foo.scpt (assuming that it contains return "foo") also returns foo), and 2) osascript writes to stdout.

So, again, there is something happening that blocks the return value from being returned from osascript /Path/To/foo.app/Contents/Resources/Scripts/main.scpt. The question is: what? Is this "by design" (and what is the rationale),or is there some bug in the chain (KM, macOS,...)?

I think your statement:

Might be on the right track, except it appears that macOS might be getting a return value from osascript that the script has specified, but macOS or some other process in the chain is preventing it from being propagated. Again, by design, or...?

[I post this question in the KM forum, because KM is in the chain...]

The cases you cite are writing a string to STDOUT.

(Scripts and functions return a value of some AppleScript type to the interpreter's evaluation space)

You asked "to where or what would it [which I understood to mean "an application in general"] return that value", and I replied that stdout and stderr would be likely candidates, and it is clear that osascript does write a result to stdout.

What exactly are you referring to by "interpreter?"

I don't know what you mean by "evaluation space." I've never heard that term. Please explain what you are referring to. Thanks.

AppleScript is an interpreted language, and Script Editor, for example provides a kind of
Read–eval–print loop for expressions written in the AppleScript language.

The evaluation space is the memory area allocated to evaluating AppleScript expressions like "2+2" into AppleScript data types like integer or real.

Expressions and functions return a value within the evaluating context.

An application is not embedded in an AppleScript evaluation, but it can interact with broader contexts by writing values out of its own evaluation space through ports to file systems and other contexts in the surrounding equipment. In other words through IO.

I have found the cause of the problem and a solution.

The cause of the problem is:

Using open /Users/Me/...MyApp.app/ in an Execute shell script KM action does open, and execute, the app, but control immediately returns to KM. KM continues executing the actions in the macro, without waiting for a reply from MyApp.app. So, if MyApp.app returns a value, and the action is configured to observe the return value from MyApp.app, KM doesn't see it, as the observation occurs immediately after the open command executes, and the macro may have finished executing before MyApp.app executes its return statement; but in any case, the observation has occurred with nothing being returned.

The solution is:

Use open -W /Users/Me/...MyApp.app instead. man open reveals:

-W Causes open to wait until the applications it opens (or that were already open) have exited.

The effect is that KM waits until MyApp.app completes, and if MyApp.app returns a value (and the action is configured to observe the return value), KM receives it.

Along the way I learned:

1. osascript, the AppleScript interpreter, does indeed write to stdout and stderr: from man osascript:

...
a.scpt:
on run argv
   return "hello, " & item 1 of argv & "."
end run

% osascript a.scpt world
hello, world.
...
-s flags

Modify the output style. ...

h  Print values in human-readable form (default).
...
osascript normally prints its results in human-readable form...

e  Print script errors to stderr (default).
o  Print script errors to stdout.

osascript normally prints script errors to stderr, so downstream clients only see valid results. When running automated tests, however, using the o modifier lets you distinguish script errors, which you care about matching, from other diagnostic output, which you don't.

While writing to stdout is inferred by this description, this behavior is easily verifiable by invoking osascript MyApp.app, where MyApp.app uses AppleScript's return keyword to return something, in Terminal.

2. At completion of script execution, osascript returns the result of the last expression.

A script that explicitly uses return somevalue prints the string equivalent of the somevalue value to stdout.

There is no indication that any sort of "evaluation space," "AppleScript evaluation," "the interpreter's evaluation space," or the like, is at work. I still have no idea what these terms are intended to refer to.

So, the moral of the story is: when using Execute a shell script to execute an app that is an AppleScript wrapped in an app wrapper, that you want to acquire input focus (which is the reason to use open), and that returns a value that is of interest to the macro, use open -W /Path/To/MyApp.app/ (and, note the trailing '/').

Hello @Etaoinsh :wave:

You maybe forgot that you can use scripting to talk to the terminal and to Keyboard Maestro & Keyboard Maestro Engine. If you do everything including preparing the right Parameters for the Macro to act on as a base - then you just need the Activate a Specific Application Action and/or the corresponding Triggers to launch the AppleScript Applet that triggers the Macro or let the Macro got triggered from Launching the AppleScript Applet and use underlying code to talk back and forth between AppleScript Applet and Macro as Long as you want in a variety of different ways.

Greetings From Germany

Tobias

Wolf suggested the same thing upthread (if I understand correctly what you mean, that is)...as I replied at that time, I really wanted to understand what the issue was, and also, I want to minimize dependencies of the script/app on other apps...but thanks for chiming in.

Hello @Etaoinsh

For Keyboard Maestro & ~ Engine you will definitely have to rely on if you want to have the AppleScript Applet acting differently based on the Macros Controlflow and on how the Macro was triggered.

There is no other way they could safely talk to each other…. In this but only this case the terminal is just a bonus …

But if the Terminal is in the mix of your workflow I would definitely consider doing the scripting with AppleScript on the Terminal Application as well …

I would say it first depends on the way you work but it also depends on what you want to achieve..

I mean it’s all suggestions… if you want to have more flexibility and power to the workflow then take my suggestions and act on them - the possibilities are endless on what you can do with it …

Greetings from Germany

Tobias