How to Use Environment Variables in launchd plist Files on macOS: A Complete Guide


2 views

When working with launchd on macOS, you might encounter situations where you need to reference environment variables in your plist configuration. While launchd doesn't directly expand shell variables in ProgramArguments, there are several effective workarounds.

The main issue arises because launchd plist files are XML documents parsed by the system, not shell scripts. When you try to use something like $HOME in ProgramArguments, it's treated as a literal string rather than being expanded:

<array>
    <string>/bin/sh</string>
    <string>$HOME/bin/script.sh</string>  // Won't work!
</array>

Here are three reliable approaches to solve this problem:

1. Using Absolute Paths

The simplest solution is to use the full path:

<array>
    <string>/Users/yourusername/bin/attach-devroot.sh</string>
</array>

2. Environment Variables Section

Add an EnvironmentVariables dictionary to your plist:

<key>EnvironmentVariables</key>
<dict>
    <key>HOME</key>
    <string>/Users/yourusername</string>
</dict>

3. Wrapper Script Approach

Create a wrapper script that handles the environment variables:

#!/bin/sh
$HOME/bin/attach-devroot.sh

Then reference the wrapper in your plist:

<array>
    <string>/path/to/wrapper.sh</string>
</array>

You can force shell expansion by explicitly calling the shell:

<array>
    <string>/bin/sh</string>
    <string>-c</string>
    <string>$HOME/bin/script.sh</string>
</array>
  • Always use absolute paths when possible
  • Consider using ~/Library/LaunchAgents for user-specific agents
  • Test your plist with launchctl unload and launchctl load
  • Check logs with console app if things don't work

Here's a full working example that combines several techniques:

<?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>com.example.myapp</string>
    
    <key>ProgramArguments</key>
    <array>
        <string>/bin/sh</string>
        <string>-c</string>
        <string>source ~/.profile && $HOME/bin/myapp --daemon</string>
    </array>
    
    <key>RunAtLoad</key>
    <true/>
    
    <key>StandardOutPath</key>
    <string>/tmp/myapp.log</string>
    
    <key>StandardErrorPath</key>
    <string>/tmp/myapp.err</string>
</dict>
</plist>

When working with launchd plist files on macOS, you might encounter situations where you need to reference environment variables like $HOME in your ProgramArguments. However, launchd doesn't directly expand environment variables in the plist XML file.

Unlike shell scripts, launchd plist files don't perform environment variable expansion by default. The XML parser treats the text literally, so $HOME/bin/attach-devroot.sh won't be expanded to /Users/yourusername/bin/attach-devroot.sh.

Here are three effective approaches to handle environment variables in your launchd configuration:

1. Using a Wrapper Shell Script

Create a simple shell script that expands the variables and then executes your command:

#!/bin/sh
exec "$HOME/bin/attach-devroot.sh"

Then reference this wrapper in your plist:

<array>
    <string>/path/to/wrapper.sh</string>
</array>

2. Setting EnvironmentVariables in the plist

You can define environment variables directly in the plist:

<key>EnvironmentVariables</key>
<dict>
    <key>HOME</key>
    <string>/Users/yourusername</string>
</dict>

3. Using launchctl setenv Before Loading

Set the environment variable before loading your plist:

launchctl setenv HOME $HOME
launchctl load ~/Library/LaunchAgents/your.plist

For maximum reliability, I recommend combining methods 2 and 3:

<?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>com.example.yourprogram</string>
    <key>EnvironmentVariables</key>
    <dict>
        <key>HOME</key>
        <string>/Users/yourusername</string>
    </dict>
    <key>ProgramArguments</key>
    <array>
        <string>/bin/sh</string>
        <string>-c</string>
        <string>exec $HOME/bin/yourscript.sh</string>
    </array>
    <key>RunAtLoad</key>
    <true/>
</dict>
</plist>
  • Use launchctl getenv HOME to verify environment variables
  • Check system logs with log show --predicate 'subsystem == "com.apple.xpc.launchd"'
  • Test your script directly in Terminal first
  • Remember that launchd runs with a different environment than your user shell