Article Series

This article series discuss more than 30 different programming languages. Please read overview before you read any of the details.

Playing with Records Related Articles.

Where to Discuss?

Local Group

Preface

Goal: Utilizing JQ in BASH for string processing.

Instead of using pure bash, we can directly extract data in json format using jq.

For convenience, we can combine the output of jq command, to bash for further processing.

Reference Reading

Source Examples

You can obtain source examples here:


Common Use Case

Task: Get the unique tag string

Please read overview for more detail.

Data Structure Support

We are going to use external json file.

Prepopulated Data

Songs and Poetry

The data is simply a json file.

{
  "songs": [
    { "title": "Cantaloupe Island",
      "tags": ["60s", "jazz"]},
    { "title": "Let It Be",
      "tags": ["60s", "rock"]},
    { "title": "Knockin' on Heaven's Door",
      "tags": ["70s", "rock"]},
    { "title": "Emotion",
      "tags": ["70s", "pop"]},
    { "title": "The River"}
  ]
}

JQ in BASH Solution

The Answer

We are going to combine JQ with other tools in BASH.

jq -r "(.songs[].tags | \
       select( . != null ))[]" \
    songs.json \
  | sort \
  | uniq \
  | tr '\n' ' '

Enough with introduction, at this point we should go straight to coding.

Environment

No need any special setup. Just run and voila..!


1: Array in JQ

We are going to check how far associative array in bash, can handle data structure.

Simple Array

Consider begin with simple array.

json='{
  "tags": [
    "rock", "jazz", "rock", "pop", "pop"
  ]
}'
tags=$(echo $json | jq -r .tags[])
echo ${tags[@]}

With output result as below:

❯ ./01-tags.sh
rock jazz rock pop pop

JQ: Simple Array

No problem so far.

Associative Array

Instead of using interal data, we better go straight to json file as below:

{
  "title": "Cantaloupe Island",
  "tags": ["60s", "jazz"]
}

Now consider this form.

jq -r .title song.json
jq -r .tags song.json

With output result as below:

❯ ./02-song.sh
Cantaloupe Island
[
  "60s",
  "jazz"
]

JQ: Output associative array

We still have no problem with code above. But unfortunately we cannot go further than this.


2: Songs JSON

We need to prepare our song json.

Records in Songs JSON

The json data is simply as shown below:

{
  "songs": [
    { "title": "Cantaloupe Island",
      "tags": ["60s", "jazz"]},
    { "title": "Let It Be",
      "tags": ["60s", "rock"]},
    { "title": "Knockin' on Heaven's Door",
      "tags": ["70s", "rock"]},
    { "title": "Emotion",
      "tags": ["70s", "pop"]},
    { "title": "The River"}
  ]
}

JQ: The Songs JQ Containing List of Record

Testing JQ Result

Now we can have a very short code. With the result exactly the same as above code:

title=$(jq -r .songs[1].title songs.json)
echo $title

tags=( $(jq -r .songs[1].tags[] songs.json) )
echo ${#tags[@]}
echo ${tags[@]}

With the result as below lines of string.

JQ: Using Songs Module

❯ ./03-songs.sh
Let It Be
2
60s rock

3: Finishing The Task

Extract, Flatten, Unique

Extracting Data Structure

Based on the result above, we can go further, extracting the tags data.

We need to filter lines without tags. And get rid of it.

path='.songs[].tags'
remove='select( . != null )'
jq -r "$path | $remove" songs.json

[] With the result of array of array, as shown below.

❯ ./04-extract.sh
[
  "60s",
  "jazz"
]
[
  "60s",
  "rock"
]
[
  "70s",
  "rock"
]
[
  "70s",
  "pop"
]

JQ: Extracting Comma Separated String

Notice how we remove the unwanted record.

  remove='select( . != null )'

Flatten

Now we can normalize, the separated values. Flatten all values into just single array.

Using two loops. One after another.

path='.songs[].tags'
remove='select( . != null )'

tags=$(jq -r "($path | $remove)[]" songs.json)
echo ${tags[@]}

With the result of a flattened array shown below.

❯ ./05-flatten.sh
60s jazz 60s rock 70s rock 70s pop

Flatten Rewrite

We can also rewrite the variable in bash, so the code can become more readable. Although it is not necessary.

join='., " "'
path='.songs[].tags'
remove='select( . != null )'

filter="($path | $remove)[] | $join"
tags=$(jq -r "$filter" songs.json)
echo ${tags[@]}

With the result exactly the same as previous flattened array.

JQ: Flattening Array

Unique

Instead of solving the approach using pure jq, I prefer to use other tools as necessary.

jq -r "(.songs[].tags | \
       select( . != null ))[]" \
    songs.json \
  | sort \
  | uniq \
  | tr '\n' ' '

With the result similar as below array:

❯ ./06-uniq.sh
60s 70s jazz pop rock

JQ: Solving Unique Song

It is so easy to learn, if you willing to read the offical documentation.

That is all.


What is Next 🤔?

Consider continue reading [ Awk - Playing with Records ].