0

If I have a simple bash script set_token.sh:

#!/bin/bash

output='export AWS_ACCESS_KEY_ID="111"
export AWS_SECRET_ACCESS_KEY="222"
export AWS_SESSION_TOKEN="333"'

echo "$output" | while read line; do eval $line; done

Executed set_token.sh did not successfully set the 3 environment variables. However if I run eval on each line separately, it works.

$ eval 'export AWS_ACCESS_KEY_ID="111"'
$ eval 'export AWS_SECRET_ACCESS_KEY="222"'
$ eval 'export AWS_SESSION_TOKEN="333"'

Why is that so?

codeforester
  • 39,467
  • 16
  • 112
  • 140
Tony Vu
  • 4,251
  • 3
  • 31
  • 38
  • 5
    You set the variables in the environment of a subshell when you pipe; see [BashFAQ 24](http://mywiki.wooledge.org/BashFAQ/024). – Benjamin W. Feb 07 '17 at 06:40
  • @Tony Vu: Are you planning to make us clear on your requirement? Is your problem solved or you stuck? – Inian Feb 07 '17 at 08:33
  • 1
    After you fix [BashFAQ #24](http://mywiki.wooledge.org/BashFAQ/024), then executing your script will set the three variables... and exit, with the newly assigned variables disappearing with the death of the shell they were set in. `export`ing a variable modifies the environment of *child* processes, not parent processes. – Charles Duffy Feb 08 '17 at 03:36

3 Answers3

3

You can achieve the desired result without a loop and without eval.

source <(echo "$output")

The <() construct is a process substitution. It executes the command found inside, creates a FIFO (special first-in, first-out file), and is then transformed into an actual file path (pointing to the FIFO) which source can read from.

Of course, you could also store the actual assignments in a file rather than putting them in the output variable.

source config_file

The source command (or its more standard form .) reads commands from a file and executes them in the current shell, without launching a separate process or subshell, so variable assignments in sourced files work. Useful for config files, but of course you must be sure no one can put arbitrary commands in those files as that would be a security risk.

IMPORTANT

If you want to put declarations in a script (set_token.sh in your case), this script must be sourced (i.e. executed with source or .), not executed with bash or by calling it directly (if it is executable). Any method other than source or . will launch a child process, and there is no way for a child process to assign variables that will be visible to the parent process afterwards. Sourcing does not create a separate process, which is why assignments will work. The export keyword will make assignments visible to children process only, they cannot make assignments visible to the parent.

Fred
  • 6,590
  • 9
  • 20
  • Thanks, but it did not work – Tony Vu Feb 08 '17 at 01:55
  • 2
    @TonyVu Please see the IMPORTANT section I added to my answer, which I strongly suspect explains why it did not work for you (because the technique itself works, there is no doubt about that). – Fred Feb 08 '17 at 03:07
  • 1
    Hi @Fred, using source or . works. – Tony Vu Feb 08 '17 at 06:26
  • Good answer, but just to be clear: if the input comes from a _file_ anyway, `source` (`.`) is the right command; if not, just use `eval` directly on the string (which also works with multiple lines); `source` with a process substitution offers no advantage in that scenario - on the contrary: it involves a subshell. _`source` and `eval` are equally risky_: they blindly evaluate whatever commands are handed to them (in the context of the current shell). – mklement0 Feb 09 '17 at 03:34
1

Not sure why you want to use eval in this case. Why not set the variables more directly like this:

export AWS_ACCESS_KEY_ID="111"
export AWS_SECRET_ACCESS_KEY="222"
export AWS_SESSION_TOKEN="333"

Your loop is running in a sub shell (because of echo "$output" | ...) and that's why your variables are not visible outside. It's not that eval is not working! Don't worry - this happens to a lot of people.

If you are insistent on using the loop and eval, you could use process substitution < <(command):

while read line; do eval $line; done < <(printf "%s\n" "$output")
Community
  • 1
  • 1
codeforester
  • 39,467
  • 16
  • 112
  • 140
  • `++` for right ways, btw embedded your answer in mine, to emphasize not using `eval` and recommend alternate ways. – Inian Feb 07 '17 at 07:00
  • Thanks. In my case, the variables are generated dynamically. I just used the output here for simplicity. This seems to be the correct reason, but the process substitution still did not work for me. – Tony Vu Feb 07 '17 at 07:00
  • @TonyVu: What else are you looking for then? – Inian Feb 07 '17 at 07:02
  • @TonyVu: Can you give a case of `variables are generated dynamically`, ? Provide a minimal example to test with – Inian Feb 07 '17 at 07:07
  • @Inian: I mean replace my last line in the script with @codeforester' s suggestion `while read -r line; do eval $line; done < <(printf "%s\n" "$output")`, but the environment variables are still not set – Tony Vu Feb 07 '17 at 07:19
  • @TonyVu - I did test the loop before posting. It did work for me. Are you sure you are setting `output` variable correctly? – codeforester Feb 07 '17 at 07:21
  • @TonyVu: I think you need to re-think your strategy. When you said `variables are generated dynamically`? How is it done? Why are you storing multiple variables with `export` in another single variable? Do you have valid reasons for that? – Inian Feb 07 '17 at 07:22
  • @Inian was that part relevant to this question? Say I read the 3 lines of export from a text file. – Tony Vu Feb 07 '17 at 09:47
  • Are you just reading `name=value` pairs? and adding export yourself or export is available in the file itself? Can you provide a sample file – Inian Feb 07 '17 at 10:53
  • export is also read from the file – Tony Vu Feb 07 '17 at 14:49
0

Fred's helpful answer contains a viable solution and good pointers (and the problem with the original approach is explained in Bash FAQ #24 - "I set variables in a loop that's in a pipeline. Why do they disappear after the loop terminates?").

That said, in your specific scenario - assuming you're willing to accept the risk of using eval - you can apply it directly to your multi-line string:

#!/bin/bash

output='export AWS_ACCESS_KEY_ID="111"
export AWS_SECRET_ACCESS_KEY="222"
export AWS_SESSION_TOKEN="333"'

# This defines all 3 AWS_* environment variables.
eval "$output"

To reiterate Fred's point: For the environment variables to take effect in the current shell, you must source the script (using builtin . or its (effective) alias source):

. ./set_token.sh
Community
  • 1
  • 1
mklement0
  • 382,024
  • 64
  • 607
  • 775