56

If I have a JSON like this,

{
    "hello1": "world1",
    "testk": "testv"
}

And I want to export each of these key-value pairs as environment variables, how to do it via shell script? So for example, when I write on the terminal, echo $hello1, world1 should be printed and similarly for other key-value pairs? Note: The above JSON is present in a variable called $values and not in a file.

I know it will be done via jq and written a shell script for this, but it doesn't work.

for row in $(echo "${values}" | jq -r '.[]'); do
    -jq() {
        echo ${row} | jq -r ${1}
    }
    echo $(_jq '.samplekey')
done
Charles Duffy
  • 280,126
  • 43
  • 390
  • 441
Qirohchan
  • 1,057
  • 1
  • 9
  • 15
  • Possible duplicate of [How to convert a JSON object to key=value format in jq?](https://stackoverflow.com/questions/25378013/how-to-convert-a-json-object-to-key-value-format-in-jq) – iamauser Jan 30 '18 at 02:40
  • @iamauser I don't think it is a duplicate since the question is not just about parsing but getting the pairs into the current bash environment. – Turn Jan 30 '18 at 02:41
  • 2
    You shouldn't edit your question to add an answer. If what you did is sufficiently different from an answer you've received, you should self-answer instead with separate answer. – Benjamin W. Jan 30 '18 at 03:30
  • 1
    I use this to convert `json` to `.env` file `cat prod.json | jq -r '.[] | "\(.OptionName)=\(.Value)"' > prod.env` – Khaled AbuShqear Aug 16 '20 at 10:16

8 Answers8

68

Borrowing from this answer which does all of the hard work of turning the JSON into key=value pairs, you could get these into the environment by looping over the jq output and exporting them:

for s in $(echo $values | jq -r "to_entries|map(\"\(.key)=\(.value|tostring)\")|.[]" ); do
    export $s
done

If the variables being loaded contain embedded whitespace, this is also reasonable, if slightly more complex:

while read -rd $'' line
do
    export "$line"
done < <(jq -r <<<"$values" \
         'to_entries|map("\(.key)=\(.value)\u0000")[]')
Morgen
  • 1,010
  • 1
  • 11
  • 15
Turn
  • 6,656
  • 32
  • 41
  • 1
    when I run the script attached in the question, it gives me an error saying, `parse error: Invalid numeric literal at line 1, column 8`. Do you think there is some problem with the input? – Qirohchan Jan 30 '18 at 03:00
  • 1
    @Qirohchan The test you added to your original question didn't work because you are using double quotes both for the outer expression and the keys and values inside. Try this instead: `values='{"hello1": "world1","testk": "testv"}'` – Turn Jan 30 '18 at 03:08
  • 1
    that worked perfectly. I just wanted to ask, in my actual code, this JSON will be produced by another command. So, if the JSON is empty, i.e. the variable `values` is empty, will this script throw an exception and therefore will I have to add an additional check for empty `values`? Or the loop will simply not run and there will be no exception? – Qirohchan Jan 30 '18 at 05:12
  • 2
    @Qirohchan Why not try it and find out? – Turn Jan 30 '18 at 05:38
  • so the current code seems to break when I try to have an input having `.` in the input. That is, see my edit in the original question and try running the script. It says, `-bash: export: hello1.world1.abc1=hello2.world2.abc2': not a valid identifier`. – Qirohchan Jan 30 '18 at 18:20
  • @Qirohchan : Hmm ;) The error has got nothing to do with my command. The problem here is key `hello1.world1.abc1` is not a valid bash identifier. To test this just run `export hello1.world1.abc1=hello2.world2.abc2` and see what bash tells you. The problem here is that characters like `.` have special meanings in shell and it usually expands to the current directory in which you're in. Check [\[ this \]](http://tldp.org/LDP/Bash-Beginners-Guide/html/sect_03_02.html#sect_03_02_02) link too which explains the standard practice of setting a variable. – sjsam Jan 31 '18 at 06:39
  • 8
    you don't need the `for` loop. `export` can take multiple arguments, so you can do: `export $(echo $values | jq -r "to_entries|map(\"\(.key)=\(.value|tostring)\")|.[]")` – Kenneth Nov 06 '19 at 20:06
  • Running shellcheck on the `while read` block gives this error "SC2163: This does not export 'line'. Remove $/${} for that, or use ${var?} to quiet." I changed it to "${line?}" and it worked without the warning. – chrish Mar 16 '21 at 17:15
  • What does the $'' syntax mean to read -rd ? so the command has a blank delimiter? – Brian Horakh May 12 '21 at 04:38
  • i recommend adding single quotes to the .value to avoid errors like these => `parse error near &` – Akhil Dec 08 '21 at 15:31
  • `jq --raw-output 'to_entries|map(.key+"="+.value)|join("\n")'` – Tamir Dec 26 '21 at 13:54
  • 1
    `jq` can format strings in various ways using its `@...` string formatters. In the case of this answer, you can use `@sh` with string interpolated values to protect them for use in shells by quoting them and escaping special characters, like this: `jq -r 'to_entries|map("\(.key)=" + @sh "\(.value|tostring)")|.[]'`. – ack Jan 24 '22 at 09:45
  • @ack I prefer `.value|@json`. Works well with the shell too – smac89 Jul 29 '22 at 18:39
  • `export $s` is only going to work in special cases. Consider JSON like `{"foo": "uh oh"}`. – chepner Jan 28 '23 at 15:25
11

Using command substitution $() :

# $(jq -r 'keys[] as $k | "export \($k)=\(.[$k])"' file.json)
# echo $testk
testv

Edit : Responding to this comment

You should do

$( echo "$values" | jq -r 'keys[] as $k | "export \($k)=\(.[$k])"' )

Just mind the double quotes around $values

Note: Couldn't confirm if there is security implication to this approach, that is if the user could manipulate the json to wreak havoc.

sjsam
  • 21,411
  • 5
  • 55
  • 102
  • 1
    I did this, `values='{"hello1":"world1","hello1.world1.abc1":"hello2.world2.abc2","testk":"testv"}'` and then, `echo $values | jq -r 'keys[] as $k | "export \($k)=\(.[$k])"'`. It seems to only print the export commands, but when I echo, it doesn't work. – Qirohchan Jan 30 '18 at 18:27
  • You need to enclose that in `$( )` – sjsam Jan 30 '18 at 19:05
  • enclose the values you mean? – Qirohchan Jan 31 '18 at 05:33
  • @Qirohchan exactly, In your case it should be `$( echo $values | jq -r 'keys[] as $k | "export \($k)=\(.[$k])"' )` – sjsam Jan 31 '18 at 05:44
  • I ran the command on terminal, and it says, `-bash: export hello1=world1 export hello1.world1.abc1=hello2.world2.abc2 export testk=testv: command not found` – Qirohchan Jan 31 '18 at 05:56
7

Another way, without using jq, is to parse the json with grep & sed:

for keyval in $(grep -E '": [^\{]' my.json | sed -e 's/: /=/' -e "s/\(\,\)$//"); do
    echo "export $keyval"
    eval export $keyval
done

Explanation:

  • First, grep will filter all "key" : value pairs (value can be "string", number, or boolean).
  • Then, sed will replace : with =, and remove trailing ,.
  • Lastly, exporting the "key"=value with eval

Here's an output example, exporting json keys, from an AWS record-set:

export "Name"="\052.apps.nmanos-cluster-a.devcluster.openshift.com."

export "Type"="A"

export "HostedZoneId"="Z67SXBLZRQ7X7T"

export "DNSName"="a24070461d50270e-1391692.us-east-1.elb.amazonaws.com."

export "EvaluateTargetHealth"=false

Community
  • 1
  • 1
Noam Manos
  • 15,216
  • 3
  • 86
  • 85
  • Thanks, I also had to add `mg` regex flags to replace trailing `,` so the script is `for keyval in $(grep -E '": [^\{]' secrets.json | sed -e 's/: /=/' -e "s/\(\,\)$mg//"); do echo 'export $keyval' && eval export $keyval; done;` – mexanichp Oct 05 '20 at 06:10
5

None of the existing answers preserve whitespace in the values in a POSIX shell. The following line will use jq to take each key:value of some JSON and export them as environment variables, properly escaping whitespace and special characters.

2023-01-28: BUGFIX UPDATE:

My previous answer did not work for all possible values and could cause errors. Please instead use the following line, which uses jq's @sh format string to properly escape values for the shell. You must also enclose everything after eval in quotes to preserve newlines. I've updated the sample JSON file to include more characters to test with.

This answer now appears to be the only one that handles all cases. There are no loops and it's one line to export all values. The downside is that it uses eval, which is theoretically dangerous... but because the entire key=value is now being escaped for the shell, this should be safe to use.

New answer (use this one):

eval "export $(echo "$values" | jq -r 'to_entries | map("\(.key)=\(.value)") | @sh')"

Old answer (don't use this one):

eval export $(echo "$values" \
  | jq -r 'to_entries|map("\"\(.key)=\(.value|tostring)\"")|.[]' )

edit thanks @Delthas for pointing out a missing 'export'

Sample JSON file:

bash-5.2$ cat <<'EOJSON' > foo.json
{
 "foo_1": "bar 1",
 "foo_2": "This ! is ' some @ weird $text { to ( escape \" here",
 "foo_3": "this is some \nsample new line\n    text to\ntry and escape"
}
EOJSON

Sample script:

bash-5.2$ cat <<'EOSH' > foo.sh
values="`cat foo.json`"
eval "export $(echo "$values" | jq -r 'to_entries | map("\(.key)=\(.value)") | @sh')"
export
echo "foo_2: $foo_2"
echo "foo_3: $foo_3"
EOSH

Running the sample script:

bash-5.2$ env -i sh foo.sh
export PWD='/path/to/my/home'
export SHLVL='1'
export foo_1='bar 1'
export foo_2='This ! is '"'"' some @ weird $text { to ( escape " here'
export foo_3='this is some 
sample new line
    text to
try and escape'
foo_2: This ! is ' some @ weird $text { to ( escape " here
foo_3: this is some 
sample new line
    text to
try and escape

Pros:

  • no need for Bash
  • preserves whitespace in values
  • no loops
  • (update) properly escapes all values for use in the shell

Cons:

  • uses eval, which is considered "unsafe". however, because jq is escaping all input, this is unlikely to cause a security issue (unless jq is found to have a bug which does not properly escape data using the @sh filter).
Peter
  • 71
  • 1
  • 5
  • This is a great solution, but there seemed to be an extraneous pair of quotes when I tried it. This worked for me: `eval $(echo "$values" | jq -r 'to_entries|map("\(.key)=\(.value|tostring)")|.[]' )` – metadaddy Jan 05 '22 at 01:35
  • 1
    I can't send an edit suggestion (queue is full), but the command at the top of the message should be `eval export [...]` instead of `eval [...]` – Delthas Jun 17 '22 at 11:03
  • That con should be listed at the top of the answer; this is *not* safe for untrusted input. – chepner Jan 28 '23 at 15:30
  • @chepner i have added additional detail to describe how this particular use of `eval` should be safe-ish – Peter Feb 01 '23 at 22:52
1

The approach illustrated by the following shell script avoids most (but not all) problems with special characters:

#!/bin/bash

function json2keyvalue {
   cat<<EOF | jq -r 'to_entries|map("\(.key)\t\(.value|tostring)")[]'
{
    "hello1": "world1",
    "testk": "testv"
}
EOF
}

while IFS=$'\t' read -r key value
do
    export "$key"="$value"
done < <(json2keyvalue)

echo hello1="$hello1"
echo testk="$testk"

Note that the above assumes that there are no tabs in the keys themselves.

peak
  • 105,803
  • 17
  • 152
  • 177
  • I don't want to make a new function for this, but make it a part of the existing script itself. I did this, but it doesn't work this way. `values='{"hello1":"world1","hello1.world1.abc1":"hello2.world2.abc2","testk":"testv"}' while IFS=$'\t' read -r key value do export "$key"="$value" done < < (echo $values | jq -r 'to_entries|map("\(.key)\t\(.value|tostring)")[]') `. Where am I going wrong? – Qirohchan Jan 31 '18 at 05:58
  • There must not be a space before `(echo` – peak Jan 31 '18 at 07:12
1

jtc solution:

export $(<file.json jtc -w'[:]<>a:<L>k' -qqT'"{L}={}"')
  • in the walk argument, either the initial `[:]` iteration, or the recursive atomic search `<>a:` is superfluous - the solution will work if only either of them given. Though with the atomic search the solution will be _invariant_ (will work if outer encapsulations are given, or if inner structures are present), thus preferable. – Dmitry Aug 20 '20 at 10:40
1

I've come up with a solution (here in bash):

function source_json_as_environ() {
  eval "$(jq -r '
  def replace_dot:
    . | gsub("\\."; "_");
  def trim_spaces:
    . | gsub("^[ \t]+|[ \t]+$"; "");
  to_entries|map(
    "export \(.key|trim_spaces|replace_dot)="
    + "\(.value|tostring|trim_spaces|@sh)"
    )|.[]' $@)"
}

And you can use it like this:

$ source_json_as_environ values.json
Djabx
  • 635
  • 1
  • 8
  • 16
0

here's a improved version based on above answers, that handles well spaces and line breaks:

export ENVS='{"BASE_URL": "JUICEFS_CONSOLE_URL/static", "CFG_URL": "JUICEFS_CONSOLE_URL/volume/mount"}'
for keyval in $(echo $ENVS | sed -e 's/": "/=/g' -e 's/{"//g' -e 's/", "/ /g' -e 's/"}//g' ); do
    echo "export $keyval"
done

export ENVS='{"BASE_URL": "JUICEFS_CONSOLE_URL/static",
    "CFG_URL": "JUICEFS_CONSOLE_URL/volume/mount"}'
for keyval in $(echo $ENVS | sed -e 's/": "/=/g' -e 's/{"//g' -e 's/", "/ /g' -e 's/"}//g' ); do
    echo "export $keyval"
done

export ENVS='{"BASE_URL": "JUICEFS_CONSOLE_URL/static",   "CFG_URL": "JUICEFS_CONSOLE_URL/volume/mount"}'
for keyval in $(echo $ENVS | sed -e 's/": "/=/g' -e 's/{"//g' -e 's/", "/ /g' -e 's/"}//g' ); do
    echo "export $keyval"
done
timfeirg
  • 1,426
  • 18
  • 37