2

Summary

How can I execute scripts from the system-wide Script menu with a normal environment? It seems the environment is not being setup at all.

Scripts that are run from the script menu can find and execute commands from the base system. However, user-installed utilities are not found on the path. Also, /usr/bin/env foo is not finding commands that are installed outside of the base system.

Background

The system Script menu appears at the right side of the menu bar. It is enabled via Script Editor.app > Preferences > Show Script menu in menu bar. It displays scripts located in ~/Library/Scripts and other system locations. The menu can execute AppleScript, JXA, bash, python and other scripts (use shebang as needed).

enter image description here

Investigation So Far

Scripts that are run from the Script menu don't inherit the user's bash environment. They know the shell is BASH and your USER name, but no initialization occurs. I ran a little script to dump the env to a text file

~/Library/Scripts/dump_env.sh (be sure to make executable):

#!/bin/bash
env > ~/env.txt
ps -ef >> ~/env.txt

Here are the interesting entries.

USER=mat
LOGNAME=mat
SHELL=/bin/bash
PATH=/usr/bin:/bin:/usr/sbin:/sbin
PWD=/
HOME=/Users/mat
_=/usr/bin/env

It is very vanilla. None of the custom paths are included. For example, the following are unavailable:

/usr/local/bin/svn # Subversion source control client
/opt/local/bin/python3 # python3 installed via MacPorts
~/bin

The latter two are normally sourced in my .bash_profile. /usr/local/bin/ is setup by path_helper which is called by /etc/profile (though apparently not for the Script menu).

I'm running macOS 10.13 High Sierra, but I'm interested whether others are getting different results.

Ideal Solution

  1. Avoid hard-coding path to executables in shebang. Ex: if I install a different python version / distribution, I want to use it in all my scripts. /usr/bin/env python3 accomplishes this, but it seems to depend on the PATH?
  2. Finds utilities in /usr/local/bin
  3. Specify PATH once for CLI and Script menu (and GUI?). If I install a new Python I would like it to be used everywhere.
  4. Specify PATH only for user-level processes. I don't want system processes using binaries from in my add-on path entries (especially user-permissioned path entries).
  5. Avoid breaking 3rd party builds/installers/programs that were written with assumptions about what is present on my macOS install.

I'm looking into launchctl, which can be used to setup environment for GUI apps. Not sure if it still works or if it will work with the Script menu.

Update


I added trace statements to all of the bash environment files: /profile, /bashrc, ~/.bash_profile, ~/.bashrc. These statements export environment variable so I can see that the file has been sourced. None of these are getting run for scripts run from the Script menu.

launchctl setenv KEY VALUE will set an environmental variable for all processes subsequently launched by launch services (launchd) in the user space.

This works for Terminal.app, GUI applications and scripts that are double-clicked in the Finder. However, it does not work for the Script menu.

I added ps -ef to the dump_env.sh script. This tells me the Script menu is not invoking bash with any arguments that would strip the environment (such as -r or -p). The parent process is UserScriptService.

Running…

otool -tV /System/Library/Frameworks/Foundation.framework/Versions/C/XPCServices/com.apple.foundation.UserScriptService.xpc/Contents/MacOS/com.apple.foundation.UserScriptService.

…reveals a symbol named __NSUserScriptTaskServiceStart. This sounds awfully similar to NSUserScriptTask in the CoreFoundation API. From the API doc:

The NSUserScriptTask class is able to run all the scripts normally run by the one of its subclasses…

https://developer.apple.com/documentation/foundation/nsuserscripttask?language=objc

I strongly suspect this is what the Script menu uses to execute scripts. The API doc does says nothing about the script's runtime environment.

4
  • Can't you just source .bashrc et all from your script?
    – nohillside
    Feb 2, 2021 at 10:51
  • nohillside — Yep, sourcing .bashrc works well for bash scripts. I'm transitioning my scripts to python, so my main case is getting something like #!/usr/bin/env python3 working. I need the environment to find python3. I tried a bash script with a here-doc for python; it works, but that breaks editor syntax coloring. That probably breaks the python debugger too.
    – Mat Gessel
    Feb 2, 2021 at 11:05
  • 1
    Why not specify the path to python3 directly then?
    – nohillside
    Feb 2, 2021 at 11:58
  • I don’t understand the premise of “normal environment” the graphical script editor is for AppleScript and JXA so you’re going to need to handle arbitrary shells that don’t invoke an interactive login like you do with cron or launchd tasks. (With references I suppose this could be the answer?)
    – bmike
    Oct 30, 2021 at 12:20

1 Answer 1

1

bash can startup in three different ways and how it uses rc files is different in each case.

To give a short answer when it is called as a login shell it reads .profile or .bash_profile to set things like PATH. When it is called as an interactive shell but not a login shell, say you run bash from the command line, then it reads ~/.bashrc to set these things.

If it's run from a shell script (or by launchctl) then it looks for an environment variable BASH_ENV and runs the file named in the variable.

Check https://www.gnu.org/software/bash/manual/html_node/Bash-Startup-Files.html

To answer your question you should set all variables in .bashrc, call that in your .bash_profile file with the line if [ -f ~/.bashrc ]; then source ~/.bashrc; fi Finally in .bashrc you should set the BASH_ENV variable export BASH_ENV='.bashrc'

This would then give you a PATH and so on everywhere.

When you move to the zsh shell things are slightly different, read http://zsh.sourceforge.net/Intro/intro_3.html

Most Mac admins and engineers ignore it all and we hard code the path to all our software in scripts. I've certainly never seen an Apple script that didn't have all the tool paths hard coded.

5
  • Unfortunately, all of this is being ignored by the system Script menu. See my update to the question. It seems we need a fourth case: invoking bash via the Objective-C API. I'm currently using a kluge where I specify my own environment initialization script in the shebang. Some caveats here: stackoverflow.com/a/9988385/199296
    – Mat Gessel
    Feb 2, 2021 at 10:47
  • Were you setting and exporting BASH_ENV and what were you setting it to? I suspect launchctl won't respect a tilde for home, for example. Feb 11, 2021 at 1:49
  • I have a ~/Library/Launch Agents/bashenv.plist that specifies launchctl setenv BASH_ENV /Users/mat/.bashrc. It also /bin/sh -c /Users/mat/.bashrc for good measure. • I need to post another update — my guess about NSUserScriptTask stripping the environment was wrong. The env is lost only when running scripts from the Script menu in the system-wide NSStatusBar. • I wonder if it's fixed in newer editions of macOS. Do you have a few minutes to try running the dump_env.sh script in my question above from your Script menu?
    – Mat Gessel
    Feb 11, 2021 at 2:02
  • Sorry, I don't even use bash anymore, I'm on zsh. Feb 11, 2021 at 11:28
  • Hi @TonyWilliams I said the quiet part out loud in my comment. Would you steal my words as a preface to your awesome answer? The nub seems to be like cron and launchd, there is no interactive log in process to set environment since the GUI script menu is for JXA and AppleScript, not the command line.
    – bmike
    Oct 30, 2021 at 12:23

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .