54

So I have a bash script which outputs details on servers. The problem is that I need the output to be JSON. What is the best way to go about this? Here is the bash script:

# Get hostname
hostname=`hostname -A` 2> /dev/null

# Get distro
distro=`python -c 'import platform ; print platform.linux_distribution()[0] + " " +        platform.linux_distribution()[1]'` 2> /dev/null

# Get uptime
if [ -f "/proc/uptime" ]; then
uptime=`cat /proc/uptime`
uptime=${uptime%%.*}
seconds=$(( uptime%60 ))
minutes=$(( uptime/60%60 ))
hours=$(( uptime/60/60%24 ))
days=$(( uptime/60/60/24 ))
uptime="$days days, $hours hours, $minutes minutes, $seconds seconds"
else
uptime=""
fi

echo $hostname
echo $distro
echo $uptime

So the output I want is something like:

{"hostname":"server.domain.com", "distro":"CentOS 6.3", "uptime":"5 days, 22 hours, 1 minutes, 41 seconds"}

Thanks.

Justin
  • 42,716
  • 77
  • 201
  • 296
  • 7
    Is there a reason you don't want to do this all from Python? I mean, you're using it anyway, and Python is can easily determine the hostname, read the uptime, and generate the JSON you want. – willglynn Sep 21 '12 at 04:53
  • See [this newer question](https://stackoverflow.com/questions/48470049/build-a-json-string-with-bash-variables) but which has better quality answers. Note that building a valid JSON requires properly handing special characters in the input like `"` and `\n`, and simple string concatenation does not guarantee that. – jakub.g Sep 30 '22 at 11:01

7 Answers7

105

If you only need to output a small JSON, use printf:

printf '{"hostname":"%s","distro":"%s","uptime":"%s"}\n' "$hostname" "$distro" "$uptime"

Or if you need to produce a larger JSON, use a heredoc as explained by leandro-mora. If you use the here-doc solution, please be sure to upvote his answer:

cat <<EOF > /your/path/myjson.json
{"id" : "$my_id"}
EOF

Some of the more recent distros, have a file called: /etc/lsb-release or similar name (cat /etc/*release). Therefore, you could possibly do away with dependency your on Python:

distro=$(awk -F= 'END { print $2 }' /etc/lsb-release)

An aside, you should probably do away with using backticks. They're a bit old fashioned.

Steve
  • 51,466
  • 13
  • 89
  • 103
  • 16
    `printf` would be a bit cleaner: `printf '{"hostname":"%s","distro":"%s","uptime":"%s"}\n' "$hostname" "$distro" "$uptime"` – glenn jackman Sep 21 '12 at 10:31
  • Upvote for printf since it seems to work on BSDs as well. At least in OSX, `echo` doesn't have the '-e' option from what I can see in the manual. – Michel Müller Mar 09 '15 at 07:53
  • 1
    Upvoted for the comment re backticks. Not only are they old-fashioned , but they are hard to read, and most importantly non-nestable. Nested command-substitution is an occasionaly used but very powerful mechanism. – Graham Nicholls Apr 23 '18 at 18:41
  • `echo -e` should generally Not Be Used. The POSIX sh specification disallows it (or, rather, permits it to have no behavior other than printing `-e` on stdout -- unlike most bashisms, this one doesn't just extend the standard but actively breaks it); and bash may or may not honor it depending on the active runtime configuration; see [Why is `printf` better than `echo`?](https://unix.stackexchange.com/a/65819) on [unix.se], and/or the [POSIX `echo` specification](https://pubs.opengroup.org/onlinepubs/9699919799/utilities/echo.html), which itself recommends using `printf` instead! – Charles Duffy Apr 21 '20 at 02:14
  • @CharlesDuffy: Agreed. I have removed the `echo -e` solution. Never expected this answer to get much attention... or any of my answers for that matter.. – Steve Apr 21 '20 at 03:03
58

I find it much more easy to create the json using cat:

cat <<EOF > /your/path/myjson.json
{"id" : "$my_id"}
EOF
Barmar
  • 741,623
  • 53
  • 500
  • 612
Leandro Mora
  • 851
  • 6
  • 3
4

I'm not a bash-ninja at all, but I wrote a solution, that works perfectly for me. So, I decided to share it with community.

First of all, I created a bash script called json.sh

arr=();

while read x y; 
do 
    arr=("${arr[@]}" $x $y)
done

vars=(${arr[@]})
len=${#arr[@]}

printf "{"
for (( i=0; i<len; i+=2 ))
do
    printf "\"${vars[i]}\": ${vars[i+1]}"
    if [ $i -lt $((len-2)) ] ; then
        printf ", "
    fi
done
printf "}"
echo

And now I can easily execute it:

$ echo key1 1 key2 2 key3 3 | ./json.sh
{"key1":1, "key2":2, "key3":3}
Jimilian
  • 3,859
  • 30
  • 33
1

@Jimilian script was very helpful for me. I changed it a bit to send data to zabbix auto discovery

arr=()

while read x y;
do
    arr=("${arr[@]}" $x $y)
done

vars=(${arr[@]})
len=${#arr[@]}

printf "{\n"
printf "\t"data":[\n"

for (( i=0; i<len; i+=2 ))
do
     printf "\t{  "{#VAL1}":\"${vars[i]}\",\t"{#VAL2}":\"${vars[i+1]}\"  }"

    if [ $i -lt $((len-2)) ] ; then
        printf ",\n"
    fi
done
printf "\n"
printf "\t]\n"
printf "}\n"
echo

Output:

    $ echo "A 1 B 2 C 3 D 4 E 5" | ./testjson.sh
{
    data:[
    {  {#VAL1}:"A", {#VAL2}:"1"  },
    {  {#VAL1}:"B", {#VAL2}:"2"  },
    {  {#VAL1}:"C", {#VAL2}:"3"  },
    {  {#VAL1}:"D", {#VAL2}:"4"  },
    {  {#VAL1}:"E", {#VAL2}:"5"  }
    ]
}
Daro
  • 31
  • 1
  • 5
1

I wrote a tiny program in Go, json_encode. It works pretty good for such cases:

$ ./getDistro.sh | json_encode
["my.dev","Ubuntu 17.10","4 days, 2 hours, 21 minutes, 17 seconds"]
Fedir RYKHTIK
  • 9,844
  • 6
  • 58
  • 68
1
data=$(echo  " BUILD_NUMBER : ${BUILD_NUMBER} , BUILD_ID : ${BUILD_ID} , JOB_NAME : ${JOB_NAME} " | sed 's/ /"/g')

output => data="BUILD_NUMBER":"29","BUILD_ID":"29","JOB_NAME":"OSM_LOG_ANA"
Boris Verkhovskiy
  • 14,854
  • 11
  • 100
  • 103
  • 2
    Welcome to Stack Overflow. Rather than just posting the code, it would be beneficial to elaborate on it a bit. Try explaining what is wrong with the code in the question, and how your code fixes it. – sampathsris Sep 04 '19 at 02:30
0

To answer the subject line, if you were to needing to to get a tab separated output from any command line and needed it to be formatted as a JSON list of lists, you can do the following:

echo <tsv_string> | python3 -c "import sys,json; print(json.dumps([row.split('\t') for row in sys.stdin.read().splitlines() if True]))

For example, to get a zfs list output as json:

zfs list -Hpr -o name,creation,mountpoint,mounted | python3 -c "import sys,json; print(json.dumps([row.split('\t') for row in sys.stdin.read().splitlines() if True]))
Timothy C. Quinn
  • 3,739
  • 1
  • 35
  • 47