The Keyboard Maestro scripting environment

Friend of the blog Jason Verly was up late a couple of nights ago, trying to get either SnapClip or SnapSCP working. The embedded Python script was crapping out very early, throwing an error when it tried to run the

import Pashua

line. Jason knew damned well he’d installed both the Pashua app and the Pashua Python module, but the script kept failing. Even more frustrating was that he could run a script from the Terminal with the import Pashua line and it would work just fine.

After a bit of sleep—which often helps—Jason realized that the problem arose because he had two Pythons installed on his computer, the stock one from Apple and another installed via Homebrew. His usual shell environment, which ran under Terminal, was set up to run the Homebrew Python in /usr/local/bin by default, and when he installed the Pashua module, it was put in /usr/local/lib/python2.7/site-packages, which is where the Homebrew Python can find it but not where the stock Python can find it. And Keyboard Maestro was running the stock Python when the macro was invoked.

The explanation can be found in the Keyboard Maestro documentation on scripting:

Shell scripts can execute any installed scripting language, such as perl, python, ruby or whatever. Be aware that because shell scripts are executed in a non-interactive shell, typically none of the shell configuration files (like .login or .profile) will be executed, which may change the way the shell script behaves.

Here’s an example of how different the behavior can be. If I run

echo $PATH

from Terminal, I get this output:

/Users/drdrang/anaconda/bin:/usr/local/mysql/bin:
/usr/local/git/bin:/Users/drdrang/Dropbox/bin:
/opt/local/bin:/usr/local/mysql/bin:/usr/local/bin:
/usr/local/sbin:/usr/local/texlive/2010/bin/x86_64-darwin:
/Developer/usr/bin:/usr/local/bin:/usr/bin:/bin:
/usr/sbin:/sbin:/opt/X11/bin

which I’ve reformatted from one single line into a series of lines. PATH is the environment variable that determines the directories your shell searches for executable files and the order in which they’re searched. So when I type python at the command line in Terminal, the Python that gets executed is the one in /Users/drdrang/anaconda/bin because that’s the first directory in the list that contains an executable named python.

The PATH is set through commands in any number of dotfiles that get run whenever I open a Terminal window. These include ~/.profile, ~/.bash_profile, ~/.bashrc, and ~/.bash_login. And those are only my personal configuration files. There are also system configuration files in /etc. If you want to see how this mess works, I suggest you take a look at this post.

Now let’s set up a simple macro that runs the same echo $PATH command, but from within Keyboard Maestro.

PATH in Keyboard Maestro

The output from this is quite sparse:

/usr/bin:/bin:/usr/sbin:/sbin

This is why Jason’s macros weren’t doing what he expected. They were running the stock Python because /usr/local/bin isn’t in the PATH under Keyboard Maestro.

The obvious solution to this, assuming you don’t want to run the stock Python, is to explicitly set the full path to the Python you want to run in the shebang line of the script, e.g.,

#!/usr/local/bin/python

I’ve always been happy to just run the stock Python inside Keyboard Maestro. This means any nonstandard libraries have to be installed via /usr/bin/pip or

/usr/bin/python setup.py

so they go into /Library/Python/2.7/site-packages where the stock Python can find them.

But what if I want Keyboard Maestro to run the same Anaconda Python that I typically run from the Terminal? In theory, I could start my embedded scripts with

#!/Users/drdrang/anaconda/bin/python

Unfortunately, I like to have a common set of Keyboard Maestro macros shared between my iMac at work and my MacBook Air. And because of an historical fluke, I use different user names on the two machines. So while the shebang line above would work on my MacBook Air, it would have to be

#!/Users/realname/anaconda/bin/python

on the iMac. But because they’re synced, the macros have to be the same on both machines.

The solution is to use the env command in the shebang line. From the man page:

The env utility executes another utility after modifying the environment as specified on the command line.

Modifying the Keyboard Maestro environment is exactly what we want to do. We can use env’s -S and -P options to set the PATH for the execution of python this way:

#!/usr/bin/env -S -P${HOME}/anaconda/bin python

The -S option allows the HOME environment variable to be interpreted, and the -P option sets the PATH. The PATH will therefore be either

/Users/realname/anaconda/bin

or

/Users/drdrang/anaconda/bin

depending on which computer I’m at.

Thanks to Jason for pointing out this tricky business and for coming up with the simpler solution. He is, I believe, is too smart to have different user names on different computers.

Update 03/2/2017 9:52 PM
A few things you should read if you’re interested in Keyboard Maestro and environments in general:

  • Jason’s written a more thorough description of the trouble he ran into and how he fixed it.
  • You can set the PATH Keyboard Maestro uses to anything you like by going into KM’s Preferences and doing a little editing. Thanks to Peter Lewis (who’s kind of an expert on KM) for the tip.
  • For a more complete discussion of the crazy, nutso system of setting the environment on a Mac, see this post by Rob Wells.