An Emacs package that provides some helpful functions for working with environment variables and env files.
This package uses a bash subprocess to fully expand variables, which means you can leverage the power of bash to define variables.
Create an env file with the following contents at ~/.env/foo
:
FOO=~/foo
BAR=$FOO/bar
BAZ=$(pwd)
Now, run M-x environ-set-file
, which will prompt for a file. Navigate to
~/.env/foo
and press Enter. Voilà, you have three new environment variables
set in Emacs. Verify you can retrieve them with M-x getenv
. Unset them with
M-x environ-unset-file
(this will again prompt for a file).
Besides setting (and unsetting) environment variables from env files, this package provides an API for common operations with environment variables. Examples below.
[interactive] Set environment variables defined in the file at FILE-PATH.
When used interactively, prompts for the file to load. The prompt begins in
environ-dir
. When used from elisp, FILE-PATH can either be absolute or
relative to default-directory
.
(environ-set-file (expand-file-name "~/.env/foo"))
[interactive] Unset the environment variables defined in FILE-PATH.
See the documentation for environ-set-file
.
(environ-unset-file (expand-file-name "~/.env/foo"))
Set environment variables defined in the given string STR.
Parse STR like an env file. STR is split into newline-delimited lines, where each line is a key/value pair.
(environ-set-str "A=foo\nB=$A-bar")
(getenv "A") ;; => "foo"
(getenv "B") ;; => "foo-bar"
Unset environment variables defined in string STR.
Parse STR like an env file. STR is split into newline-delimited pairs, where the key of each pair is the environment variable name. The value of each pair is discarded, as the environment variable will be unset regardless of its value.
(environ-unset-str "A=foo\nB=$A-bar")
(getenv "FOO") ;; => nil
(getenv "BAR") ;; => nil
Return all current environment variables as a list of pairs.
(environ-get-pairs)
;; => (("LANG" "en_US.UTF-8")
;; ("HOME" "/Users/cfclrk")
;; ...)
Set the environment variables defined by the given PAIRS.
PAIRS is a list of pairs, where each pair is an environment variable name and value.
(environ-set-pairs '(("A" "foo")
("B" "$A-bar")))
(getenv "A") ;; => "foo"
(getenv "B") ;; => "foo-bar"
;; Prevent interpolation using single quotes
(environ-set-pairs '(("A" "foo")
("B" "'$A-bar'")))
(getenv "A") ;; => "foo"
(getenv "B") ;; => "$A-bar"
Unset the environment variables defined in the given PAIRS.
PAIRS is a list of pairs, where each pair is an environment variable name and value. The value in each pair doesn't matter; each environment variable will be unset regardless of its value.
(getenv "A") ;; => "foo"
(environ-unset-pairs '(("A" "foo")
("B" "bar")))
(getenv "A") ;; => nil
Return a list of all current environment variable names.
(environ-get-names)
;; => ("HOME" "FOO" "BAR" ...)
Unset environment variables with the given NAMES.
NAMES is a list of environment variable names which may or may not be currently
set. This function removes each name from process-environment
if it is set.
(getenv "A") ;; => "foo"
(environ-unset-names '("A" "B"))
(getenv "A") ;; => nil
[interactive] Unset the environment variable NAME.
Unset the given environment variable by removing it from process-environment
if it is there. Note that calling setenv
with a prefix argument can unset a
variable by setting its value to nil, but the variable remains in
process-environment
. This function completely removes the variable from
process-environment
.
Directory to prompt for env files.
This variable is only used by environ-set-file
and environ-unset-file
when
they are run interactively. Defaults to (expand-file-name "~/")
.
(setq environ-dir (expand-file-name "~/.env"))
A list of functions to run before shell evaluation.
Each function takes a list of pairs and returns an updated list of pairs.
Defaults to nil
.
(setq environ-pre-eval-functions
'((lambda (pairs)
(cons '("A" "a") pairs))
(lambda (pairs)
(cons '("B" "b") pairs))))
A list of functions to run after shell evaluation.
Each function takes a list of pairs and returns an updated list of pairs.
Defaults to '(environ-ignore-bash-vars)
.
(setq environ-post-eval-functions
'((lambda (pairs)
(cons '("A" "a") pairs))
(lambda (pairs)
(cons '("B" "b") pairs))))
Each line in an env file must be in a KEY=VALUE
format, with one entry per
line. This package invokes an bash
shell to interpret the file, so shellisms
should work (like ~
expansion or using single quotes to prevent variable
interpolation).
For example:
A=foo
B="bar"
C='R$%!$KP$'
D=$A-bar
E=~/cats
This example shows one way to set environment variables in an org
document
using a table:
#+NAME: env
| Var | Value |
|------+-----------------|
| FOO | ~/foo |
| BAR | $FOO/bar |
| BAZ | '$FOO/bar' |
#+begin_src emacs-lisp :var env=env
(environ-set-pairs env)
#+end_src
This package works by evaluating the provided input in a bash subprocess, and then returning the difference between current environment and the subprocess environment.
You can think of each function in terms of two phases:
- Run bash - (parse input, create a bash script, and run it)
- Update Emacs env vars - (parse the bash output and set each env var)
In the first phase, input is parsed into a list of pairs (the IR
) if it isn't
already in that form. Then, pre-eval-functions (if any) are run, which creates a
new list of pairs (also IR
). The IR
structure is then assembled into a bash
script and executed. The last thing the bash script does is run printenv
, so
that's what the stdout is.
flowchart LR
env-file[env file] -- parse --> IR
IR -- pre-eval-functions --> IR
IR -- build script --> script[bash script]
script -- run script --> stdout
In the second phase, we start with two inputs: the stdout that was produced by
running printenv
in the bash process, and the current Emacs
process-environment
. The bash stdout is parsed back into a list of pairs
(IR_1
) and all post-eval functions (if any) are run, which may update IR_1
.
Emacs' current process-environment
is parsed into a separate list of pairs
(IR_2
). Then, the two IRs are compared, and only elements in IR_1
that are
not in IR_2
are kept (IR_3
). Finally, each pair in IR_3
is set in the
current process-environment
.
flowchart LR
stdout -- parse --> IR_1
IR_1 -- post-eval-functions --> IR_1
cur[current environment] -- parse --> IR_2
IR_1 -- diff --> IR_3
IR_2 -- diff --> IR_3
IR_3 -- setenv each pair --> done