Set Variable Environment Variables in bash (or other)

export $key=$val should work just fine in bash. I suspect your issue is the pipe (|). All commands in a pipeline are executed in a subshell. In your example, the instance of bash that is running your script will fork off 2 subshells: one for cat $1 and another for the while read .... The variables are assigned and exported in the subshell running the while loop, and then all of them are promptly thrown out when the subshell exits. One way to fix this is to not spawn subshells at all. Instead of a useless use of cat, try redirection instead:

while read ...; do
   ...
done < "$1"

BashFAQ 24 explains this in much greater detail.

An alternate approach is to just source the file, with exports thrown in:

. <(sed '/^export/!s/^/export /' "$1")

The <( ) is process substitution.

As an additional note of caution, since you are reading environment variables from an argument, you must make sure that the argument file $1 is a trusted source so it can't do bad things to your script.


(jw013 has already given a correct answer, but I want to point out a few more issues.)

The right-hand side of a pipeline runs in its own subshell in bash (as well as most other shells, the exceptions being ATT ksh and zsh). So you're setting the environment variables in the subshell that executes the loop, but not in the main shell process.

Here there's a easy fix which is to replace the useless use of cat by a redirection:

while read …; do …; done <"$1"

In general, if you need to preprocess the input, put the loop and the remainder of the script (at least the part that needs the changed variables) inside a block.

grep '^foo_' "$1" | {
  while read …; do …; done
  do_something
}

Note that in general, while read line; do … does not quite iterate over the input lines. See Why is while IFS= read used so often, instead of IFS=; while read..? for an explanation. The correct idiom to iterate over input lines is

while IFS= read -r line; do …

Here, the removal of leading and trailing whitespace doesn't matter since there shouldn't be any given your sample input, but you may need -r to avoid backslash expansion. It's possible that while read line is in fact a correct way to read your input (but I suspect only if the variable values contain no newlines). It's also possible that your input format is ambiguous or more complex to parse. Check what happens if one of the variables' value contains a newline character, a double quote or a backslash.

One point where you are definitely mangling the input is when you extract the value:

val=`echo ${kv#*=} | sed 's/^"\|"$//g'`

First, you need to use double quotes around the variable substitution: echo "${kv#*=}"; otherwise the shell performs word splitting and filename generation (i.e. globbing) on it. Second, echo is not a reliable way to print a string, because some strings that begin with a - are treated as options, and some shells perform backslash expansion on the argument. A reliable and portable way to print a string is printf %s "${kv#*=}". Also, if the value spans multiple lines, the sed program will act on each separately, which is wrong. This is fixable, but there's an easier method, which is to use the shell's string manipulation facilities: val=${kv#*=}; val=${val#\"}; val=${val%\"}.

Assuming that your input is correctly parsed with a plain read, here's a simpler way to parse it, taking advantage of the IFS setting to split each line:

while read name value; do
  value=${value#\"}; value=${value%\"}
  export name="$value"
done <"$1"