3

Note: This general topic seems to be covered ad nauseum on more than one SE site, but please read through all the details — I don't find any questions which cover all the criteria described below.

Also: I'm using zsh in the examples below. I'm hoping for a POSIX-compliant solution, but that's not a hard requirement: something that works for bash and zsh is good enough.

Here's an example to help clarify the question title:

Let's say I have the following two files:

% ls -AF
.env    printenv*

% cat printenv
echo $LOCAL_MESSAGE_1
echo $LOCAL_MESSAGE_2

% cat .env
LOCAL_MESSAGE_1="hello world"
LOCAL_MESSAGE_2="goodnight moon"

and I'd like to execute the script printenv and see the following in stdout:

hello world
goodnight moon

If those variables don't exist in my environment, the script prints empty lines (as expected):

% unset LOCAL_MESSAGE_1

% unset LOCAL_MESSAGE_2

% ./printenv


If I set the variables before executing the script, they become available to it, but only for that single process, and not to any subsequent processes launched from the same shell:

% LOCAL_MESSAGE_1="hello world" LOCAL_MESSAGE_2="goodnight moon" ./printenv
hello world
goodnight moon

% ./printenv


So, my question is this: How can I replicate the behavior in the last example according to the following criteria?

  1. run printenv without modification in the current shell — not in a subshell (thanks RudiC) — and get the desired output, while also
  2. loading the variables from the .env file, while also
  3. keeping the variables local to that process invocation: they will remain unset in the shell environment and to subsequent processes without needing to unset them

What single line syntax can I use to accomplish this? What should be substituted in place of the question marks below to meet the criteria above?

% ?????????? ./printenv
hello world
goodnight moon

3 Answers 3

2

Have you tried to run those in a subshell (in bash)? Like

( . .env; . ./printenv )
hello world
goodnight moon

That way, the LOCAL variables are set for the printenv script only, and the parent shell's environment remains unchanged.

With your new, extended requirements (no subshell), try an approach using the (unwelcome, dangerous) eval :

eval $(< .env) ./printenv
hello world
goodnight moon

It puts the variables' definition and the command in one line, then evals and executes that line.

5
  • This is good, and made me realize that I failed to define a criterion, which is that the process itself needs to run in the current shell. I'll update the question to include that. Sorry about that — and thanks!
    – jsejcksn
    Commented Dec 5, 2022 at 10:57
  • Is there perhaps a way to source the variables text file in a subshell and then print/interpolate them into a string that is then evaluated inline in the parent shell before invoking the script — like in the example command that manually sets them prior to execution ($ var1=val1 var2=val2 ./script)?
    – jsejcksn
    Commented Dec 5, 2022 at 11:06
  • You can't convey back variables / values from sub to parent. Look at my second approach in my extended answer.
    – RudiC
    Commented Dec 5, 2022 at 16:15
  • Thanks. I understand the dangers of unsanitized string evaluation, but this was exactly what I was looking for. Can you explain a bit more — or better — link me to documentation about how the < operator works here when used with command substitution and eval?
    – jsejcksn
    Commented Dec 5, 2022 at 16:31
  • 1
    man bash: "... The command substitution $(cat file) can be replaced by the equivalent but faster $(< file)."
    – RudiC
    Commented Dec 5, 2022 at 16:40
0

With runenv (which is part of a package called perp - aka the perpetrator):

runenv env_file command

will
"run command without modification in the current shell — not in a subshell — and get the desired output, while also loading the variables from the env_file, while also keeping the variables local to that process invocation: they will remain unset in the shell environment and to subsequent processes without needing to unset them"

-1

You can source your scripts to your shell using ". script" syntax:

. .env && . ./printenv

You can also add the ". .env" to the beginning of your printenv script. Then you will be able to exec printenv as:

./printenv

'printenv' is not good name choice because it is used by shell.

2
  • 1
    This approach sets the LOCAL_MESSAGE variables which is undesired.
    – RudiC
    Commented Dec 5, 2022 at 10:51
  • @jonasmike This comment is accurate. Also: "'printenv' is not good name choice because it is used by shell": of course, you're right: it's not a good idea to shadow process names that are commonly available — that was just the first name that came to mind in the context of my question, so I went with it. Thanks for pointing out good practices!
    – jsejcksn
    Commented Dec 5, 2022 at 11:35

You must log in to answer this question.

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