4

Say I have this .env file:

A=1
B=2
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? – Arkadiusz Drabczyk 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 04:24
  • @ArkadiuszDrabczyk I'd like to avoid sourcing the file, to avoid catastrophic results of evaluating arbitrary bash code. – Charles Langlois 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). – Charles Langlois Sep 25 '19 at 17:57
  • FWIW, set -a; . ./.env; set +a (as in the answer I've deleted because you "don't want to source .env") is much safer than prepending export to each line and then sourcing it. If you want your file to be "simply key-value" then please clearly define its syntax, especially how values containing newlines, quotes, other metacharacters etc are supposed to be represented. –  Sep 25 '19 at 19:26
  • a possibly safer variant of the source+process subst+sed above is to 1. source .env then 2. . <(sed -E -n 's/^\s*([[:alpha:]_][[:alnum:]_]*)=.*/export \1/p' < .env) which just transforms the variable assignments into exports without the assignment. – cas Sep 26 '19 at 00:52
  • sourcing the file is almost unavoidable (the alternative is parsing the file and setting the vars by indirection). BTW what you're trying to do with the export $(cat | xargs) is to reinvent eval, which is no safer than source. If you don't/can't trust your .env file then either don't use it at all or edit it until it can be trusted. – cas Sep 26 '19 at 00:53
  • @mosvy how is it much safer? It's still sourcing the file, potentially executing arbitrary bash code? – Charles Langlois Sep 26 '19 at 13:47
  • @cas I don't understand your proposition of sourcing the raw file first. How can it be safer than processing it before sourcing it? – Charles Langlois Sep 26 '19 at 13:48
  • source the file. then source the sed-modifed version of the file which only has export statements. I'm assuming that exporting the variables is your goal here, and that they're not exported in the .env file. BTW, why don't you just edit the file and add export statements where you need them? – cas Sep 26 '19 at 14:13
  • Because I want a lightweight format for specifying configuration options, not write a bash script. It's not just me using this, it's my team. And compatibility with tools that expect a .env is a factor. – Charles Langlois Sep 26 '19 at 14:31
  • 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. –  Sep 26 '19 at 19:27
  • @mosvy I'm not thinking about hackers, I'm thinking about good practices and avoiding catastrophic mistakes. All I want is a file to store key values, interpreted strictly as keys and string values. – Charles Langlois Sep 27 '19 at 16:55

2 Answers2

0

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
Ole Tange
  • 35,514
0

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 shell 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 '' (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).