How to Return String Values From Actions :-)

This is a tip. I discovered that KM actions can return string values.

WARNING: When Peter reads this he will likely say this is a dangerous technique. One possible reason he may say this is that it might cause memory leaks. So as a courtesy to Peter I won't upload the macros, I'll just post some images of actions and give the general idea so that noobs don't actually try this. But it's so fun!

(I can't really confirm if it creates a memory leak because the KM Engine is always giving me memory leaks. I have to restart it several times per day when my system grinds to a halt after the Engine is using >8GB of RAM, when my system has only 8GB of RAM. I've given up trying to fix it, I just reboot it hourly. This also helps to reset the Find Image action which often gets into a bad mood until the engine restarts.)

The basis of this technique is the underlying mechanism of how the %ActionResult% gets it value. By doing a little hypothesizing, experimenting and observing I noticed that I can return anything I want from a macro and place that result into the %ActionResult% token.

Here's the germ of the idea. Let's say you want your function to return the string "Paris" in the %ActionResult% token. Here's what you place at the end of the action:

I won't try to explain how it works, (if you're a noob you don't need to know) but I will say that I figured this out on my own. This will generate an error string which is passed back to the %ActionResult% token, and the string will contain some gobbledygook which includes the word "Paris". So in the action which gets that result returned you can easily check if the string contains the word "Paris", perhaps like this: (notice the word CONTAINS below)

Now that's the short of it. But there are a few extra details to be aware of. Firstly, you can't return the strings 0 to 31 because those strings are used by KM to return certain conditions like "OK" (more on this below). Anyone can figure out how to work around that. Secondly, there may be some characters that can't be returned. I haven't tested the entire character set but basic alphanumerics are fine and that's all I need as a programmer. Thirdly, the actual string being returned includes some gobbledygook. But it seems fairly easy to remove that superfluous text. I use this action to remove the text:

If you do the above, then you can replace CONTAINS with IS for more accurate programming.

Fourthly, did I mention the memory leak? I actually have no way of testing if it causes one. Perhaps someone here with a more stable implementation would be willing to test that idea.

Fifthly, you need to be careful that your macro doesn't actually contain any other errors as they will override this value. But if you exit the macro after setting this value it shouldn't be a problem.

In addition I should point out that when you issue the kill command it doesn't terminate the macro. In fact you can issue multiple kill commands in a macro, and only the last one's value will be returned to %ActionResult%.

I mentioned the values 0-31 earlier. These are values that KM seems to support (it uses them for its own purposes) so these values may be stable, by which I mean not cause memory leaks. But they have some odd patterns. You can see some of them return nothing but "OK", while some of them return a text string with a number (all of this goes into %ActionResult% after the parenthesized number), and if you are eagle-eyed you will notice that some numbers are missing, because the action simply aborts with no error. I suppose this might be a bug but Peter would reply "it's not a bug if it's not a feature." LOL.

* (31) Task failed with status 31
* (30) Task failed with status 30
* (29) OK
* (28) OK
* (27) Task failed with status 27
* (26) Task failed with status 26
* (24) Task failed with status 24
* (23) OK
* (20) OK
* (19) OK
* (16) OK
* (15) Task failed with status 15
* (14) Task failed with status 14
* (13) Task failed with status 13
* (12) Task failed with status 12
* (11) Task failed with status 11
* (10) Task failed with status 10
* (9) Task failed with status 9
* (8) Task failed with status 8
* (7) Task failed with status 7
* (6) Task failed with status 6
* (5) Task failed with status 5
* (4) Task failed with status 4
* (3) OK
* (2) Task failed with status 2
* (1) Task failed with status 1
* (0) OK

I haven't really decided yet if I'm going to write programs that use this trick. I do worry about its stability. Let's see what the experts have to say about this. I'm just an average user who uses KM a lot.

Even though this idea is likely to be rejected by the experts, I came up with a couple of other great ideas last week that will probably be accepted when I post them later. :slight_smile:

I forgot to mention that for Execute Shell Script action you must disable the flag "Failure Aborts Macro" (otherwise it won't work) and perhaps also "Notify on Failure" (to avoid the warnings you don't want.)

Sorry, I must be missing the point/purpose of your technique.

All KM Variables contain a string, and many KM Actions can return a string.
For example:

image

I've been using KM for 4+ years now, and I've never observed any memory leaks caused by KM.

4 Likes

Sometimes I slip up and say "action" when I meant "macro". My opening sentence should have said "I discovered that KM Macros can return string values." Is it more clear now?

I'm glad you don't have memory leaks. I infer from this statement that I'm doing something "wrong". Sure, but I don't know what it is. I guess what I should do is make sure I have no macros being triggered over a 24 hour period and see if the leak exists without any macros being run.

Nope. :wink:

I don't see anywhere in your examples where the actual Macro is returning anything.

If fact, KM Macros can not return anything. They can however:

  1. Set a KM global Variable
  2. Set the Clipboard
  3. Write a file
  4. Display text in various ways

Of course, all of these must use either a KM Variable, the System Clipboard, or a KM Named Clipboard as the source to perform those actions.

Which of those four methods that you listed do you assert that my macro uses to return data?

I'm inferring that you are saying the code I provided won't work because it isn't using one of your four methods for returning data. But its works.

I see hundreds, if not thousands, of your posts on the forums. So I don't want to get on your bad side. Often I grate people the wrong way. There's no doubt that you are an extremely capable person. Compared to you I'm a noob. How self-deprecating shall I be? I will criticize myself before I criticize you because I may need your help someday.

I don't think @JMichaelTX is saying that a method is valid only if it adheres to one of his outlined categories, but rather trying to ascertain the sort of situation one might wish to employ the method you've described here, whilst also clarifying that macros don't return values, but actions do. Your innovative method is, likewise, not going to have a macro return a value, but does use an action to generate an error message that gets stored in the %ActionResult% token.

If I'm misunderstanding what is going on, maybe uploading your macro would make things clearer. I can't see anything particularly dangerous about this method, but I am left wondering two things:

  1. What advantages or differences does this:


    convey over the method I'm more used to of acquiring output from (for example) a shell script action that returns the word "Paris" and stores it in the variable Result?

  2. If I understand what's happening correctly, and the key event that sends the word "Paris" from your action to the %ActionResult% token (along with some gobbledygook) is having the shell script throw an error, then you can throw an error in bash by exiting a script with a non-zero return code, and sending the output of any command before it to stderr, meaning it will appear in %ActionResult%:


    The command above is this:

    echo Paris >&2; exit 1
    

    This avoids one having to use kill, which might, at first glance, make some feel uneasy about accidentally killing off a process without meaning, but also prevents one being able to return values such as "QUIT", or "TERM" (basically, any signal name). But the clearest advantage is that it doesn't travel with any gobbledygook: %ActionResult% will contain the word "Paris", and nothing more.


I hope you aren't offended, but this method you've devised, whilst certainly inventive and creative, is not really achieving anything more than having an action return a value in the more conventional ways, and seems creates additional work to retrieve the value in order to use it compared to having it stored immediately in a variable. What do you think ?

3 Likes

I have no information about any memory leaks. Memory leaks are difficult to find since they generally are related to specific actions, but to have a memory leak generate 8GB of RAM, that means the leaking action has to execute a lot, and leak a large amount. If you can determine which actions cause the leak then maybe I can fix it. Or maybe the leak is related to something else installed on your Mac and we can determine what that is. But without more information I can't do anything much to resolve it.

It took me a while to understand what you are saying here, but I see what you mean now.

On return from an Execute a Macro, the ActionResult token contains the result of the last executed action. And if you deliberately have the last executed action fail, then that failure, and any related error message, will be available via the token.

That is a clever hack.

Of course, you could alternatively just do:

The Instance Variable is specific to this macro execution, so it will work just as well.

None of this should leak obviously, but Execute Shell Script has a lot of moving parts, involves a lot of system code, so it is certainly possible that it leaks, either in Keyboard Maestro code or in system code, and even if it does not leak, it is possible to use up system resources that are cached for long enough to appear to be a leak if you execute enough of them. I don't know if returning an error would make it any more or less likely to leak.

1 Like

You are pretty polite. I'm not offended. Your first paragraph states that my macro does "not return a value, but does use an action to generate a message that gets stored in the %ActionResult% token." To me those two clauses are the same things, i.e., "return a value" = "return a message".

Your paragraph #2 is great. It's quite an improvement over my idea. That will help me a lot. I'm glad you suggested it. I'm not really a UNIX expert. I had no idea that would work. Thanks. I'll try it out.

Consider this case: concurrent macros calling the same child macro that needs to return a variable. Global variables simply cannot work in that case. Period. They cannot work because they are not local to the context of the running macro. However the %ActionResult% value is indeed local, so it will work. And even if global variables did work, I could probably reduce my global variable usage by 10% to 20% using my approach above. Considering that I have thousands of variables that's quite a lot to gain.

I see Peter just posted something as I wrote this saying I could use Instance variables. I'll consider that. Even though I did read up on instance variables, I have so far failed to understand how they work. I'm just not sure if an instance variable can be accessed by the calling macro, I'll have to test that.

I've seen a ton of posts from you on these forums, and I don't want to get you mad at me either. Thanks for your help.

1 Like

Thanks for calling me "clever", Peter. You made my day. That's almost like God calling me clever.

I'll consider your suggestion. I think I'll try running the engine one day with no macros ever being invoked and see if it leaks. If so, that may suggest it's not my macros. I'll just disable everything. My main suspect is the Find Image action. Sometimes it requires an engine reboot to start working again, even when there is no apparent memory leak.

OK, I can see the appeal now. My variable landscape is getting hideously crowded as well. There's no doubt that local and instance variables are super handy because of their scope and would work well in your example case, but they do fill up the variable list in an intrusive manner that I find myself avoiding them to keep my sea of variables from overpopulating.

I actually use dictionaries as much as I can.

That isn't likely to happen. And you're welcome. Thank you for sharing your ideas.

“Result” is a global variable, whereas the ActionResult token is specific to this macro execution instance.

Instance variables on the other hand have exactly the same scope as the ActionResult token. So if you use that action, but with “Instance Result” instead, that would be the best solution for returning a result from a macro.

Actually they don't fill up the variable list. As soon as the macro that created the Local or Instance variable quits, the variables are deleted.

IAC, neither Local nor Instance variables ever show up in the KM Preferences Variables pane.

Indeed, their values don't even show up in the editor window when I'm trying to debug my macros. All I see is "empty" even when I know they aren't empty.

So I have to debug my program when all variables are Global, then once the program works, if I like it, I then have to convert the variables manually to Local. And since there's no variable declaration I have to do this every statement where I use the variable.

What am I doing wrong, in that case?

image

No macros are currently running, and I didn’t even create those two Local__ variables; they were seemingly absorbed from downloading other people’s macros for testing, and I’ve since deleted them (the macros).

It’s why I’m hesitant to create any more local or instance variables, as I thought that their lingering around was normal behaviour.

1 Like

Just to clarify a possible point of confusion: @JMichaelTX and @CJK are both right about how local/instance variables behave. @JMichaelTX, you're talking about the list of variables shown in KM's preferences, and you're right when you say any local/instance ones don't show up there:

And @CJK, you're talking about the list of variables shown in the Insert Variable menu and Insert Variable by Name actions, where they do indeed stick around seemingly forever:

I imagine the latter behavior is done to, well, facilitate inserting variable names, and it is definitely useful for that. Personally, I'm okay with this behavior, as like @JMichaelTX, I manage my variables primarily from the preference pane and only use the Insert Variable actions sparingly, and usually the one that lets you type by name to narrow them down:

01%20PM

But I also understand and sympathize @CJK's annoyance at how any variable, local or instance or otherwise, can linger around permanently in some parts of KM like this even if the macro that contained them is long gone.

1 Like

@gglick Ah, thank you! That’s cleared up my confusion completely, though left me a little disappointed.

On the upside, we can now all play a game and upload macros onto the forum for other people to test, populated with absurd variable names like LOCAL__jigglebottom and INSTANCE__cam4videos.

3 Likes

When I import a macro it should probably tell me what global variables are being used in the macro. That would help me protect my programs from breaking due to global variable conflict. I wouldn't expect this to work 100% accurately because variable names can be obscured by some programming.

If this isn't a task Peter would do, maybe a macro can be written by the community. I see it working this way: For each macro group, show as many global variables from within that group that is used in there.

I disabled all my macro groups, all 200 groups, (doing that manually took ten minutes because every time you click on the button to disable a macro group the KM Editor becomes unresponsive for about three seconds as it... recompiles?) and overnight there was no memory leak. That's great. That suggests the problem is one of my macros. Now I guess the quickest way to figure out which one is to do a binary search by enabling 100 macro groups, then 50, then 25, etc. until I find a specific group that causes a leak. That will get me close to finding the cause. However if I go through that much work I will post the results in a new thread. This is not the correct thread for that work.

Hey @Sleepy,

Three seconds is a very long time for this...

Run this from the Script Editor.app:

tell application "Keyboard Maestro"
   set macroGroupListEnabled to name of macro groups whose enabled is true
end tell

You'll get a list in the result panel that looks similar to this (but longer of course):

{"[KM]", "[TEST]", "Activity Monitor"}

Create a new script using the following code and your newly constructed enabled list.

set macroGroupListEnabled to {"[KM]", "[TEST]", "Activity Monitor"} --<<< Paste the result of the above script here.

tell application "Keyboard Maestro"
   repeat with theMacroGroup in macroGroupListEnabled
      set macro group theMacroGroup's enabled to true -- or false
   end repeat
end tell

Now by changing the boolean flag in the above script you can enable/disable your groups just a little bit faster.

Disable the Keyboard Maestro Engine.

And then run your script from the Script Editor.app.

-Chris