Handling environment variables in Emacs on OS X and Linux

speckx2 pts0 comments

Handling environment variables in Emacs on OSX and Linux • Ben Mezger

Handling environment variables in Emacs on OSX and Linux<br>July 26, 2026 |<br>emacs<br>I read the post A Couple Of Tips For Emacs on macOS, where Irreal<br>covers JTRs Emacs configuration series, and they raise two pain points<br>on OSX. One of the tips is how to ensure environment variables are<br>properly set inside Emacs, by using exec-path-from-shell. So I thought<br>I would share how I deal with it.<br>On OSX, Emacs is launched from Finder or from the Dock rather than a<br>shell, so it doesn&rsquo;t inherit your shell&rsquo;s environment the way terminal<br>Emacs would. The same can happen with Emacs on Linux if you&rsquo;re not<br>spawning the process from a shell, or if your application launcher<br>isn&rsquo;t configured to invoke from the shell.<br>One of the issues I had with exec-path-from-shell is that it was<br>still slow even after I configured my shell to expose the environment<br>variables in a non-interactive shell. I also didn&rsquo;t want Emacs to<br>invoke a subprocess at startup. So I did something similar to how Doom<br>Emacs does: I now generate a file with all the environment variables I<br>want, and make Emacs read that file and load these variables.<br>In my early-init.el, I added this code:<br>;; Load shell env snapshot generated by emacs-refresh-env.<br>;; Mirrors Doom's approach: snapshot is written once by a script running<br>;; `zsh -ic env`, so no subprocess is needed at startup.<br>(let ((env-file (expand-file-name "env" user-emacs-directory)))<br>(when (file-readable-p env-file)<br>(dolist (line (split-string (with-temp-buffer<br>(insert-file-contents env-file)<br>(buffer-string))<br>"\n" t))<br>(when (string-match "^\\([^=\n]+\\)=\\(.*\\)$" line)<br>(setenv (match-string 1 line) (match-string 2 line))))<br>(when-let* ((path (getenv "PATH")))<br>(setq exec-path (append (split-string path ":") (list exec-directory))))))<br>Then I created a script, emacs-refresh-env, which copies the<br>environment variables from my shell and puts them into the env file,<br>which Emacs reads. The script ignores some environment variables and<br>filters out Tmux/Alacritty-generated noise.<br>#!/usr/bin/env zsh<br># emacs-refresh-env: regenerate the Emacs shell env snapshot.<br># Run after changing .zshenv or .zshrc.<br>set -euo pipefail

emacs_d="$HOME/.emacs.d"<br>out="${emacs_d}/env"

# Vars to omit from the Emacs env snapshot (shell bookkeeping, etc.).<br>exclude=(<br>SHLVL<br>PWD<br>OLDPWD<br>DIRENV_DIFF<br>DIRENV_DIR<br>DIRENV_FILE<br>DIRENV_WATCHES<br>EDITOR<br>VISUAL<br>LS_COLORS<br>ALACRITTY_LOG

pattern="^(${(j:|:)exclude})="<br># Drop excluded vars and tmux/terminal garbage (e.g. leaked \033Ptmux;…ALACRITTY_LOG=…).<br>zsh -ic env grep -E '^[A-Za-z_][A-Za-z0-9_]*=' |<br>grep -Ev "$pattern" |<br>sort >"$out"<br>echo "Wrote $(wc -l "$out" | tr -d ' ') vars to $out"<br>Once the script executes, it creates the env file inside my<br>user-emacs-directory and early-init.el picks it up when starting<br>Emacs.<br>The benefit of this is that there is no external dependency on<br>exec-path-from-shell anymore, and it&rsquo;s fast, but the trade-off is<br>you need to always make sure you generate the env file in case your<br>environment variables change.<br>I have an update-all script which updates my machine, along with<br>some other dependencies, so I hooked emacs-refresh-env into it,<br>ensuring it runs periodically and picks up new environment variables.

No notes link to this note

emacs shell environment variables file from

Related Articles