How Python script is invoked determines value of $KMVAR_ Why?

$KMVAR_fname in the scripts below is the Base name from an initial "Split Path" KM action.

The first Python script is a one-liner shell command in an "Execute Shell Script" Action. It is run by the default Python 3.6 and can read the value of the $KMVAR_fname variable

The second Python script is listed in the Script window of an "Execute Shell Script" Action. It is also run by the default Python 3.6 and can only read the string '$KMVAR_fname'

The script lines look identical to me. Am I doing something wrong?

KMVAR_fname is a shell environment variable, preset by Keyboard Maestro when it launches the shell that then launches python.

In the first script, the shell interpolates the variable and runs the command “python” (finding it through the PATH), with arguments “-c” and “-n='VALUEOFfnameVARIABLE';print(n)”. Note that even those there are single quotes in there, as far as the shell is concerned it is a double quoted string.

In the second script, the shell runs the command “/usr/local/bin/python” with no arguments and python executes the script “n='$KMVAR_fname';print(n);”. But $KMVAR_fname means nothing to python, and in any event it is within a single quoted string so probably will not be interpolated (I'm not sure about that, I'm not a python expert). In the second script, you will need to use whatever python mechanism there is to read the environment variable KMVAR_fname - environment variables and python variables are not the same thing.

Your explanation is clear. I was assuming that KM interpolates the values of its variables before sending the script to the shell for execution, like this:
n='$KMVAR_fname'; >> n='the actual file name.html';
Please consider that a feature request

The approach I implemented is based on the following post in the forum:
Python Example Code?
This line close to the end of a script passed a KM variable without any interpolation that I could spot:
print ("$KMVAR_filePath");
(I tried both single and double quotes, although Python 3 allows both)
I'm hoping @ComplexPoint will shed some light on how it worked in that script.

I will also look into your suggestion to get Python to read the 'KMVAR_fname' environment variable

@peternlewis I followed up on your suggestion:

you will need to use whatever python mechanism there is to read the environment variable KMVAR_fname

Here are the Python commands that do this, and they made the script work:

#!/usr/local/bin/python 
import os
n=os.environ['KMVAR_fname']
print(n)
1 Like

It's generally a bad idea to interpolate variables within a script like that - the possible behaviour becomes almost unbounded.

If you really really want to you can use the Write to a File action to write your script file (with interpolated variables) and then execute the script file.

Yes, interpolation can work when well-formed parameters are passed to a robust script, but can transform into damaging commands if not well-formed.
I am satisfied with the solution using os.environ that I posted above.
Thanks for considering.

1 Like

I would like to set some variables from within KM macro.
And then be able to call a python script which collects the value of those variables and executes.

I'd like to write the script in VS Code and test it there and therefore would like to access the value of KM variable from outside KM (KM is running but the Python script is not invoked using "run shell script" action).

What is the best way to do so - os.environ approach is not working?

Thank you.

Upon further research I wonder if this would be the best way to achieve what I need.

import subprocess

def run_jxa(script_file, data:list):
    args = ["osascript", "-l", "JavaScript", script_file]
    for x in data:
        args.append(x)
    output = subprocess.run(args, capture_output=True)
    return output.stderr, output.stdout

KM_variable = "name_of_my_variable_in_KM"

jxa_script = """
(() => {
//--- GET A REFERENCE TO THE KM ENGINE ---
var kme = Application("Keyboard Maestro Engine");
 
//--- GET A KM VARIABLE ---
var myVar = kme.getvariable('{}');

return myVar;
})()
""".format(KM_variable)

error, var_value = run_jxa(jxa_script, [])

print(var_value)

Hey @sims,

Either JXA or AppleScript is the way to go.

You cannot access Keyboard Maestro shell variables outside of its Execute a Shell Script actions, because the variables are exported to the action at runtime.

You could use a script file – edit that file from VSC – and run it from Keyboard Maestro, but that's not the same as what you're requesting.

-Chris

1 Like

The only rock-solid way I've found to get the value of one or more KM variables to a Python script is this:

  1. prepare a KM macro named something like "Access KM variable from Python Script" which writes the desired KM variables to a predefined file like "/private/tmp/kmvar.txt"
  2. invoke that KM macro from Python using something like this:
    os.system("""osascript -e 'tell application "Keyboard Maestro Engine" to do script "Access KM variable from Python Script"' """)
  3. read the resulting file "/private/tmp/kmvar.txt" from the Python script
2 Likes

A script like this should work fine - it does for me.

#!/usr/bin/python

from subprocess import Popen, PIPE

scpt = '''
    tell app "Keyboard Maestro Engine" to getvariable "Test Variable"
    '''
p = Popen(['osascript', '-e', scpt], stdin=PIPE, stdout=PIPE, stderr=PIPE)
stdout, stderr = p.communicate("")
v = stdout
print v

It can probably be simplified further, I don't really know python.

3 Likes

Thank you @peternlewis @ccstone and @shiva88kr!

1 Like

Thanks @peternlewis. That's a very effective script from someone who "doesn't really know python" :joy:
It works fine with my Python 3.6.10 installation, however I decided to update it to use the subprocess run() command since the man page at Python subprocess states:

The recommended approach to invoking subprocesses is to use the run() function for all use cases it can handle. For more advanced use cases, the underlying Popen interface can be used directly.

Also added the "encoding" parameter to get a string output instead of a binary.

#!/usr/bin/python

import subprocess as sub

# to get the value of KM variable "temp"
scpt = '''
    tell app "Keyboard Maestro Engine" to getvariable "temp"
    '''
cmdresult=sub.run(['osascript', '-e', scpt], stdin=sub.PIPE, stdout=sub.PIPE, stderr=sub.PIPE, encoding='UTF-8')
print(cmdresult.stdout)

# to set the value of KM variable "temp"
scpt = '''
    tell app "Keyboard Maestro Engine" to setvariable "temp" to "testing from Python"
    '''
cmdresult=sub.run(['osascript', '-e', scpt], stdin=sub.PIPE, stdout=sub.PIPE, stderr=sub.PIPE, encoding='UTF-8')

Cleaner and much more direct than what I was doing earlier.

2 Likes