Setting environment variables in MacOS Big Sur

This method uses launchctl to manage environment variables for programs invoked directly from Finder.  See the launchctl man page, especially the section LEGACY SUBCOMMANDS.  It’s not entirely accurate, but that’s not unusual.  The critical subcommands are getenv, setenv, and unsetenv. The man page indicates that the export subcommand is available; it is not.

As far as I know, the procedure outlined below is still valid for Big Sur, but the question is still open.  I would not be surprised to learn that the behaviour of launchctl has changed in ways which invalidate some of this discussion.  If you find any problems, please let me know in comments.

As explained in a previous post, the now-recommended method of system variable setting is to use the setenv subcommand of launchctl, like so:

launchctl setenv M2 /usr/share/maven/bin

The strings defined in setenv statements like the above, must be string literals. There is no means of resolving variable content based on other variables. For example, you cannot define
setenv TMPDIR $HOME/tmp

Here’s the modified code in .profile to set both launchctl and the bash environment variables.
Create the following functions in .profile.
export LAUNCHCTL_ENV_VARS="$HOME/.launchctl_env_vars"
if [ -f $LAUNCHCTL_ENV_VARS ] ; then rm $LAUNCHCTL_ENV_VARS; fi

set_launchctl_env () {
eval launchctl setenv "$1" \""$2"\"
echo launchctl setenv "$1" \""$2"\" >>$LAUNCHCTL_ENV_VARS
}
unset_launchctl_env () {
eval launchctl unsetenv "$1"
echo launchctl unsetenv "$1" >>$LAUNCHCTL_ENV_VARS
}

set_env_var () {
eval export $1=\""$2"\"
set_launchctl_env "$1" "$2"
}
unset_env_var () {
unset $1
unset_launchctl_env "$1"
}

You may then use the function set_env_var to set both bash and launchctl entries. For example,
set_env_var M2_HOME "/usr/share/maven"
set_env_var M2 "$M2_HOME/bin"
set_env_var HTML_TIDY "$HOME/.tidy"

N.B. The launchd environment variables are distinct from the shell environment variables. Hence the procedure set_env_var sets them both. Note that the environment variables will immediately be available to any shell scripts or Terminal invocations, and the launchd variables should be immediately available to any applications launched from the desktop. (I’m trying to confirm this for Sonoma.)

To get the environment re-established across a login, the launchctl startup features have to be invoked. This is where the file ~/.launchctl_env_vars comes in.  Notice that in the .profile code above, we have been executing a launchctl setenv command, and immediately echoing that same command to ~/.launchctl_env_vars.

When the system starts, the launchd process finds system daemon processes to launch from /System/Library/LaunchDaemons & /Library/LaunchDaemons. When a user logs in, launchd looks for user agents to start up in /System/Library/LaunchAgents, /Library/LaunchAgents & ~user/Library/LaunchAgents. While the first two are system-owned directories, the third is owned by the individual user.

We need two files: 1) a shell script which will actually issue the launchctl setenv commands, and 2) a LaunchAgent file that will tell launchd where to find the executable, and how to run it. The shell script is available here; the LaunchAgent file is available here.

The executable: profile2launchctl

#!/bin/sh
#
cmd_list="$HOME/.launchctl_env_vars"
SLEEP_TIME=10
# Uncomment following to echo launchctl commands to stdout
# Key StandardOutPath will have to be set in the plist file
# (pbw.plist2launchctl) for output to be captured.
#ECHO_TO_STDOUT=true
#
one_cmd () {
eval "$@"
}
#
[[ -n "$ECHO_TO_STDOUT" ]] && cat "$cmd_list"
cat "$cmd_list" | while read line; do one_cmd $line; done
[[ -n "$ECHO_TO_STDOUT" ]] && echo Sleeping in profile2launchctl
#
[[ -n "$SLEEP_TIME" ]] && sleep "$SLEEP_TIME"

Notes:
The required sequence of launchctl setenv is in the cmd_list file. The commands are simply read one line at a time,  and each line is handed to eval for execution.
Permissions & Location:
To stay on the safe side, give the file -rwxr-xr-x permissions. The locations is as specified in the plist file, which must be edited to reflect this location, and the label you apply. See following description.

The LaunchAgent file: pbw.profile2launchctl

<? xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>Label</key>
<string>pbw.profile2launchctl</string>
<key>KeepAlive</key>
<false/>
<key>Program</key>
<string>{location you choose}/profile2launchctl</string>
<key>RunAtLoad</key>
<true/>
<key>UserName<key>
<string>pbw</string>
</dict>
</plist>

Notes:
The UserName field will, of course, be set to your own login name.  Likewise, the pbw prefix  in the Label can be replaced with any suitable identifier, and the log file (see following) directed to a suitable location.
If you need to debug the plist file, add the following lines.

<key>StandardOutPath</key>
<string>/tmp/plist.log</string>
<key>Debug</key>
<true/>

These lines are used in conjunction with the ECHO_TO_STDOUT variable in the executable file. If used, the StandardOutPath will point to a writable file on your system.
Permissions & Location:
Again, give the file -rwxr-xr-x permissions. Place it in your $HOME/Library/LaunchAgents directory.

Leave a Reply

Your email address will not be published. Required fields are marked *