10

I'm trying to use env to set environment variables (read from another source, say for example a file) for a subprocess. Essentially, I am attempting the following:

env VALUE=thisisatest ./somescript.sh

If, for example, somescript.sh was:

echo $VALUE

Then this would print thisisatest as expected. But I would like to load the variables from a file. I've gotten to this point:

env $(cat .vars | xargs -d '\n') ./somescript.sh

But, I run into trouble when any of the variables contain spaces (and quotes don't work as expected). So for example:

env $(echo 'VALUE="this is a test"' | xargs -d '\n') ./somescript.sh

Will error with

env: is: No such file or directory

And trying:

env $(echo 'VALUE="thisisatest"' | xargs -d '\n') ./somescript.sh

Will give me the unexpected:

"thisisatest"

I assumed this would work properly since running env VALUE="thisisatest" ./somescript.sh prints it without the quotes.

From the error, I glean that for some reason env is not understanding that the quotes mean that the value that follows should be a string. However, I'm unsure how to interpolate these vars in a way that the quotes are correctly interpreted.

Can anyone provide any hints for how I could accomplish this?

Thanks!

4 Answers4

6

You need double quote in command substitution, otherwise, the shell will perform field splitting with the result of command substitution:

$ env "$(echo 'VALUE="this is a test"')" ./somescript.sh
"this is a test"

For env reading from file, you must let the shell does field spliting, but set IFS to newline only, so your command won't break with space:

$ IFS='
'
$ env $(cat .vars) ./somescript.sh

If you want to read from file, it's better if you just source (aka dot) the file in somescript.sh:

#!/bin/bash

. .vars
: The rest of script go here
cuonglm
  • 153,898
  • Thanks! This fixes the space problem. However, it breaks with multiple variables (they all get passed in as one big argument instead of separate ones). For example: env "$(printf 'VALUE=test one\nVALUE2=test two\n' | xargs -d '\n')" printenv prints VALUE=test one VALUE2=test two instead of VALUE=test one and VALUE=test two on separate lines. Is there a way to fix this or am I using it incorrectly? – Bailey Parker Apr 17 '15 at 07:09
  • I've tried wrapping each arg in quotes env "$(printf 'VALUE=test one\nVALUE2=test two\n' | sed 's/.*/"&"/' | xargs -d '\n')" printenv, but this still fails. – Bailey Parker Apr 17 '15 at 07:14
4

This works:

env -S "`cat .vars`" command

First, in your file, you'll need to quote the variable value.

VALUE="This has spaces"

Then use the -S flag to parse the vars (which strips out the quotes).

env -S "`cat .vars`" command-refers-to-env-vars

You can't refer to $VALUE in the command, of course, because the command is evaluated by the shell prior to the action of env. To do that you'd need to use the following:

( . .vars ; echo $VALUE )

The parentheses open a subshell so that you don't pollute your shell with all the definitions in .vars. However, the variables are available only to the shell. They're not actually in the env, so processes wouldn't be able to access them with process.ENV.VALUE or getenv("VALUE") as with the env -S invocation.

You can combine both with

( . .vars ; env -S "`cat .vars`" command-refers-to-envs $VALUE )
fra-san
  • 10,205
  • 2
  • 22
  • 43
1

Actually, there's a much better way of doing this, and you can declare multiple variables without any issue.

env {'VALUE=thisisatest','FOO=this is a test'} printenv
Maybe
  • 111
  • 2
  • env VALUE='thisisatest' FOO='this is a test' printenv seems simpler still. Even though the syntax with {...} seems to work, I've never seen it before, and dropping it (and the comma) makes the command as a whole look less cluttered. It is clear from the manual that env takes one or more strings on the form name=value. – Kusalananda Nov 20 '19 at 10:47
  • It's a list format. There are edge cases where env could be parsed incorrectly, especially when not invoked from the terminal but rather things such as .desktop files. As far as clutter goes, the syntax highlighting typically given to list format helps to distinguish it quite nicely in my opinion. You can also do some neat stuff where everything in a list shares the same prefix, like --exclude=\*.{'json','js','css'} (which excludes these particular file formats). The single quotes aren't needed in this example, mind you, but it's better for syntax highlighting. – Maybe Nov 20 '19 at 12:28
  • Ah, I see now, it's actually a brace expansion! Oh, that's an odd use for it. That would obviously only work if the invoking shell knows about brace expansions. Also note that this is would be broken if there was a space after the comma in the expansion, whereas writing it "normally" would allow one to insert whitespaces between the variables for clarity (newlines, for example, if escaped). – Kusalananda Nov 20 '19 at 13:01
  • Your experience must be different from mine because syntax highlighting makes spaces between each entry unnecessary, even a bit wasteful. Backslashing in the middle of the list to continue on a new line doesn't cause any issues, I should mention. Also, if the shell is incompatible with this syntax, it honestly probably has an even more foolproof option available to it. – Maybe Nov 20 '19 at 13:42
  • Also, as far as clarity goes, it's easier to discern where the variable declaring ends and where the command begins. Very helpful if you have to declare a lot of variables at a time. Also very useful for long inclusion/exclusion argument lists with perform searches with grep. – Maybe Nov 20 '19 at 13:51
0

I think you're over-complicating things.

$ env FOO='this is a test' printenv | egrep -i foo
FOO=this is a test

By the way quotes aren't part of this issue. Use double quotes if you want, e.g., variable substitution in your value; use single quotes if you want to prevent variable substitution, etc.

Heck, use command substitution if that suits your needs. Use formatted character strings. Use both.

$ env FOO=$'this\tis a test' printenv | egrep -i foo | cat -t
FOO=this^Iis a test
$ env FOO="$(echo $'this\tis a test')" printenv | egrep -i foo | cat -t
FOO=this^Iis a test

Oops, this may be a better example of using command substitution. The idea is to put the output of some required command into the variable, not just gratuitously use command substitution where none is necessary.

$ env FOO="$(man man)" printenv | egrep -iA5 foo=
FOO=man(1)                                    man(1)



NAME
   man - format and display the on-line manual pages
Larry
  • 141