Say I have this .env file:

C="3 4 5"

If I run set -x; echo $(cat .env | xargs):

++ cat .env
++ xargs
+ echo A=1 B=2 C=3 4 5
A=1 B=2 C=3 4 5

If I run set -x; export $(cat .env | xargs):

++ cat /tmp/test.env
++ xargs
+ export A=1 B=2 C=3 4 5
+ A=1
+ B=2
+ C=3
bash: export: `4': not a valid identifier
bash: export: `5': not a valid identifier

Then I tried a lot of other tricks to try and keep or add quotes around the C value:

$ set -x; export $(cat /tmp/test.env | xargs printf %q)
+ set -x
++ cat /tmp/test.env
++ xargs printf %q
+ export ''\''A=1'\'''\''B=2'\'''\''C=3' 4 '5'\'''
bash: export: `'A=1''B=2''C=3': not a valid identifier
bash: export: `4': not a valid identifier
bash: export: `5'': not a valid identifier

No matter what I do, the C value is always split on spaces.

Edit: To clarify, a solution based on naively sourcing the .env file(most solutions from How to export variables from a file?) is severely unsafe, if the file contains any string that can be interpreted as command executon. I want my environment files to be interpreted only as key-value data.

  • 5
    What is your goal? Do you need . .env? Sep 24 '19 at 14:44
  • you could do something like this: . <(sed -E -n '/^\s*[[:alpha:]_][[:alnum:]_]*=/ s/^/export /p' < .env). The sed command adds "export " to the beginning of lines that look like a valid variable assignment, and drops all other lines from the output. there's bound to be all sorts of horrible failure modes with unexpected input, but it's OK-ish as a quick and dirty hack.
    – cas
    Sep 25 '19 at 4:24
  • @ArkadiuszDrabczyk I'd like to avoid sourcing the file, to avoid catastrophic results of evaluating arbitrary bash code. Sep 25 '19 at 17:52
  • @cas this could be an ok solution, it seems to work fine. As long as it's strictly better than what I was doing before(which didn't handle spaces in values). Sep 25 '19 at 17:57
  • 1
    @CharlesLanglois 1. don't put arbitrary bash code in it, then ;-) 2. you can still be powned in 1e9 ways via environment variables; just think about PATH or LD_PRELOAD; and that's just the beginning 3. Any ad-hoc "parsing" will break sooner or later in dangerous and ridiculous ways; think of eg. a .env file as generated by printf 'key="val\nfoo=bar"\nquux=baz\n'. That's more likely to happen than some evil haxxor trying her hand at you.
    – mosvy
    Sep 26 '19 at 19:27

Can you use parset?

parset "`perl -nE '/[^=]+/ && print "$&,"' .env`" echo ::: "`perl -pe 's/[^=]+=//' .env`"

It deals happily with input like:

vv=* ;   & " echo this is a value - not a command

xargs does understand quoting (though not in the exact same fashion as POSIX shells), but running xargs export wouldn't work as xargs can only run standalone executables, not builtins of your shell such as export as xargs is generally not a builtin command.

The unquoted $(...) shell construct itself does IFS-splitting + globbing (not the latter in zsh), and doesn't handle quotes (and you don't want the glob part here).

As your is bash, you could however get xargs to output NUL-delimited records by invoking printf '%s\0' on the records it reads from the file, and use readarray -td '\0' (needs bash 4.4+ for -d) in bash to split that instead of that split+glob operator:

readarray -td assignments < <(xargs -r < .env printf '%s\0')
(( ${#assignments[@]} == 0 )) || export -- "${assignments[@]}"

(here using the GNU-style -r option to avoid running printf if the input has no record).

Not the answer you're looking for? Browse other questions tagged or ask your own question.