4

I have a script that parses a rudimentary .env file and exports its contents as environment variables, before taking further action:

set -eu

test -f .env && load_dotenv

exec ./foobar x y z "$@"

However, this results in environment variables defined in .env taking precedence over environment variables defined in the user's shell. I want the opposite behavior: I want environment variables explicitly set in the shell to take precedence over anything defined in the .env file.

Therefore I was hoping to "save" the current environment before running load_dotenv, and then "re-apply" that saved environment after running load_dotenv:

set -eu

user_env="$( env )"

test -f .env && load_dotenv

reapply_env "$user_env"  # ???

exec ./foobar x y z "$@"

How do I implement reapply_env, such that it can handle arbitrary environment variables? As far as I know, any byte other than \0 is valid in an environment variable, so I want to make sure that this works for any arbitrary env var. For example, there are legitimate (although unusual) use cases for embedded newlines in environment variables.

I'm open to shell-specific answers, but assume I am using a Bourne-flavored shell (Bash, Ksh, Zsh, etc.).

Ignore security issues. Assume I am operating in a trusted, controlled environment, and that any scenario like "tricking the developer into downloading a malicious .env file" involves a broader security problem that's beyond the scope of this question.

Edit: How to avoid overwriting env vars is a great question, but it's not what I'm interested in answering in this particular question. I can post a separate one for that, if there isn't already a duplicate.

2
  • 2
    "I want environment variables explicitly set in the shell to take precedence" - what about modifying your env loader to avoid setting a variable that's already set Oct 26, 2023 at 16:41
  • 4
    what does your load_dotenv do / how is it implemented? Can you just test there if the variable already exists before loading a new value from the file? Do you care bout the difference between exported shell variables (which end up in the environment of programs the shell runs) and non-exported ones (which don't)?
    – ilkkachu
    Oct 26, 2023 at 19:09

3 Answers 3

6

Bash has ability to test environment variable before setting it. So you can write your .env file like this:

VAR1="default value"
VAR2=${VAR2:="default value"}

Now, if you have a new session (batch or terminal) and do:

$ source .env
$ echo $VAR1 - $VAR2
default value - default value
$ VAR1="my value"
$ VAR2="my value"
$ source .env
$ echo $VAR1 - $VAR2
default value - my value

That syntax is described here: https://www.gnu.org/software/bash/manual/bash.html#Shell-Parameter-Expansion

Both ksh and zsh have similar ability, with same syntax for the task "assign value to a variable if this variable does not exist":

https://www.ibm.com/docs/en/aix/7.1?topic=shell-parameter-substitution-in-korn-posix

https://zsh.sourceforge.io/Doc/Release/Expansion.html#Parameter-Expansion

3

In Bash (unsure of minimum supported version), the export builtin emits a valid script that can be eval-ed or source-d:

#!/usr/bin/env bash

export FOO=$'a\nb'

export > ./user-env.bash

env -u FOO bash -c '
source ./user-env.bash ;
sh -c "printf \"%q\n\" \"$FOO\""
'

The output should be:

$'a\nb'

Zsh also emits a valid script, but it does not include any export declarations, whereas the Bash version includes declare -x at the start of each line. So I am still searching for a Zsh solution.

2
  • 1
    Use export -p in any POSIX or Bourne-like shell, including zsh, to get output that the same shell can parse (with export statements and proper quoting of the values). A POSIX-compliant shell emits output that any other POSIX-compliant shell can parse. Bash is not compliant unless set to POSIX mode: it emits output that it can parse, but other shells can't. Oct 27, 2023 at 11:15
  • Thanks @Gilles'SO-stopbeingevil'! It looks like the POSIX documentation is here, including -p: opengroup.org/onlinepubs/9699919799/utilities/… Oct 27, 2023 at 11:55
2

If you're on Linux, you can read /proc/self/environ to save the environment variables:

myenv=()
while IFS= read -d '' -r var; do
  myenv+=("$var");
done < /proc/self/environ

Then once you load your env file, use that array to set those values again (assuming the variable names don't contain =):

for i in "${myenv[@]}";
do
  export "$i";
done
3
  • AFAIU, that only gives you the stuff that was in the environment when the shell process was executed, not anything it read later from whatever startup files it reads
    – ilkkachu
    Oct 26, 2023 at 19:06
  • 1
    That's what OP wants, I think. My understanding: they set some environment variables in their shell and then run the script shown in the question, which also modifies the environment using the .env file, but they want the values they manually set to take precedence. This way, when they run the script, /proc/self/environ will have those values.
    – muru
    Oct 27, 2023 at 1:33
  • 2
    Beware environment variable names are not necessarily mappable to shell variable names. The environment could contain things like -a=b or a[$(reboot)]=c, or even entries without =s such as -p. Oct 27, 2023 at 13:20

You must log in to answer this question.

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