1

I'm using JQ to fetch some JSON from a quiz database and I want to parse the results. I'm trying to save a resulting array in Bash as shown below but the format is that used in JavaScript/Python with square brackets rather than Bash style.

quiz=$(curl -s https://opentdb.com/api.php?amount=3)
answers=$(echo $quiz | jq '[ .results][0][0].incorrect_answers')
correct=$(echo $quiz | jq '[ .results][0][0].correct_answer')
answers+=( $correct )

An example of what answers looks like is below:

[ "Excitement", "Aggression", "Exhaustion" ]

The correct answer is never pushed to the array due to the wrong format.

How can I convert an array of the format shown above so that it is interpreted in my script as an array.

Example of output from curl (this is not hard-coded, question and answer is different every-time):

{
  "response_code": 0,
  "results": [
    {
      "category": "Entertainment: Television",
      "type": "multiple",
      "difficulty": "easy",
      "question": "Which company has exclusive rights to air episodes of the "The Grand Tour"?",
      "correct_answer": "Amazon",
      "incorrect_answers": [
        "Netflix",
        "BBC",
        "CCTV"
      ]
    },
    {
      "category": "Celebrities",
      "type": "multiple",
      "difficulty": "medium",
      "question": "How much weight did Chris Pratt lose for his role as Star-Lord in "Guardians of the Galaxy"?",
      "correct_answer": "60 lbs",
      "incorrect_answers": [
        "30 lbs",
        "50 lbs",
        "70 lbs"
      ]
    },
    {
      "category": "Animals",
      "type": "boolean",
      "difficulty": "easy",
      "question": "The Killer Whale is considered a type of dolphin.",
      "correct_answer": "True",
      "incorrect_answers": [
        "False"
      ]
    }
  ]
}
2
  • So what do you want your bash array to contain?
    – jesse_b
    Nov 21, 2019 at 20:37
  • @Jesse_b I want the bash array to include correct_answer and incorrect_answers. Nov 21, 2019 at 20:38

5 Answers 5

5

I'd get jq to output the results line-wise. Then use the bash mapfile command to read the lines into an array

mapfile -t correct < <(jq -r '.results[] | .correct_answer' <<<"$quiz")
declare -p correct
declare -a correct=([0]="Amazon" [1]="60 lbs" [2]="True")

For the incorrect answers, since bash does not have multi-dimensional arrays, I'd get jq to output the array of incorrect answers as CSV:

mapfile -t incorrect < <(jq -r '.results[] | .incorrect_answers | @csv' <<<"$quiz")
declare -p incorrect
declare -a incorrect=([0]="\"Netflix\",\"BBC\",\"CCTV\"" [1]="\"30 lbs\",\"50 lbs\",\"70 lbs\"" [2]="\"False\"")

Then:

$ echo "q $i: ans=${correct[i]}; wrong=${incorrect[i]}"
q 1: ans=60 lbs; wrong="30 lbs","50 lbs","70 lbs"

Documentation:


Assuming you're interacting with the user:

i=0
read -p "Answer to question $i: " answer

if [[ $answer == "${correct[i]}" ]]; then
    echo Correct
elif [[ ${incorrect[i]} == *"\"$answer\""* ]]; then
    echo Incorrect
else
    echo Invalid answer
fi

Keep in mind that within [[...]] the == operator is not a string equality operator, it is a pattern matching operator.

  • the first test is a straightforward string comparison
  • the second test sees if the CVS string holding the incorrect answers contains the answer in double quotes
6
  • Are you expecting I will write the answers like "Netflix" every-time, because it will be random questions and answers each API call. Nov 21, 2019 at 20:50
  • I'm not expecting anything, I'm responding to the information contained in your question. Nov 21, 2019 at 20:52
  • In that case I apologize, I should have mentioned that the JSON I posted was an example of the format but I have no knowledge of the specific text. Nov 21, 2019 at 20:53
  • Which you store in the quiz variable, which I use in my answer. Nov 21, 2019 at 20:54
  • Any way to handle values with newlines in them? I'm still looking. Looks like I'll have to write it myself.
    – jrw32982
    Jan 21, 2021 at 16:11
2

Extending @RomanPerekhrest's answer (go upvote it now):

mapfile -t answers < <(jq -r '.results[] | [.correct_answer] + .incorrect_answers | @sh' <<<"$quiz")
declare -p answers
declare -a answers=([0]="'Amazon' 'Netflix' 'BBC' 'CCTV'" [1]="'60 lbs' '30 lbs' '50 lbs' '70 lbs'" [2]="'True' 'False'")

Then, you can use something like this

for i in "${!answers[@]}"; do
    declare -a "this_answers=( ${answers[i]} )"
    echo $i
    printf " > %s\n" "${this_answers[@]}"
done
0
 > Amazon
 > Netflix
 > BBC
 > CCTV
1
 > 60 lbs
 > 30 lbs
 > 50 lbs
 > 70 lbs
2
 > True
 > False
1

According to the stated goal "I want the bash array to include correct_answer and incorrect_answers" you expect to get an array of combined values.
No need to run 2 different jq commands, the whole combined sequence can be composed with a single jq expression:

For single-word array items:

$ declare -a answers=($(jq -r '[.results[0].correct_answer] + .results[0].incorrect_answers | @sh' <<<"$quiz"))

@sh:

The input is escaped suitable for use in a command-line for a POSIX shell. If the input is an array, the output will be a series of space-separated strings.

Results:

$ echo "${answers[2]}"
'BBC'
$ echo "${answers[0]}"
'Amazon'
$ echo "${answers[1]}"
'Netflix'
$ echo "${answers[@]}"
'Amazon' 'Netflix' 'BBC' 'CCTV'

To handle items with spaces (like those in .results[1]) use the following approach with readarray and @json option:

$ readarray -t answers < <(jq -r '[.results[1].correct_answer] + .results[1].incorrect_answers | .[] |@json' <<<"$quiz")

Results:

$ echo "${answers[1]}"
"30 lbs"
$ echo "${answers[0]}"
"60 lbs"
$ echo "${answers[@]}"
"60 lbs" "30 lbs" "50 lbs" "70 lbs"
$ echo "${answers[2]}"
"50 lbs"
11
  • Try this with .results[1] - the quotes are literal. Nov 21, 2019 at 21:03
  • Oh yeah, that's much better than mine Nov 21, 2019 at 21:04
  • This would be perfect, I'm getting black when I echo though. Nov 21, 2019 at 21:11
  • Are you sure you have valid contents in $quiz? Nov 21, 2019 at 21:12
  • Sorry I had deleted my quiz line. This works great thanks! Nov 21, 2019 at 21:13
1

Because this question is lodged in my head, a little script to run the game:

#!/usr/bin/env bash

main() {
    local -a questions
    mapfile -t questions < <(
        get_quiz |
        jq -r '.results[] | [.question] + [.correct_answer] + .incorrect_answers | @sh'
    )

    for i in "${!questions[@]}"; do
        local -a q="( ${questions[i]} )"
        question $((i+1)) "${q[@]}"
    done
}

question() {
    local num=$1 question=$2 correct=$3
    local -a answers=("${@:3}")

    # shuffle the answers
    mapfile -t answers < <(printf "%s\n" "${answers[@]}" | sort -R)

    echo
    echo "Question #$num"
    PS3="${question//&quot;/\'} "

    select answer in "${answers[@]}"; do
        if [[ $answer == "$correct" ]]; then
            echo "Correct"
            break
        fi
        echo "Incorrect"
    done
}

get_quiz() {
    # this is where you'd do your curl call, but 
    # for now, the sample data is printed.
    cat << 'JSON'
    {
      "response_code": 0,
      "results": [
        {
          "category": "Entertainment: Television",
          "type": "multiple",
          "difficulty": "easy",
          "question": "Which company has exclusive rights to air episodes of the &quot;The Grand Tour&quot;?",
          "correct_answer": "Amazon",
          "incorrect_answers": [
            "Netflix",
            "BBC",
            "CCTV"
          ]
        },
        {
          "category": "Celebrities",
          "type": "multiple",
          "difficulty": "medium",
          "question": "How much weight did Chris Pratt lose for his role as Star-Lord in &quot;Guardians of the Galaxy&quot;?",
          "correct_answer": "60 lbs",
          "incorrect_answers": [
            "30 lbs",
            "50 lbs",
            "70 lbs"
          ]
        },
        {
          "category": "Animals",
          "type": "boolean",
          "difficulty": "easy",
          "question": "The Killer Whale is considered a type of dolphin.",
          "correct_answer": "True",
          "incorrect_answers": [
            "False"
          ]
        }
      ]
    }
JSON
}

main
2
  • This is awesome, beyond what I planned to do but I'll use this now! Nov 22, 2019 at 1:13
  • 1
    Don't just use it. Understand it. Ask questions about bits you don't get. Nov 22, 2019 at 1:26
0

Assuming you want to iterate over each element in the results array and for each assign the correct_answer value to a shell variable and the incorrect_answers values to an array variable in bash:

#!/bin/bash

data=file.json

unset results
eval "$( jq -r '@sh "results=\(.results|length)"' "$data" )"
if [ -z "$results" ]; then
    echo 'Failed to parse number of results' >&2
    exit 1
fi

for (( i = 0; i < results; ++i )); do
    unset correct_answer
    unset incorrect_answers

    eval "$(
        jq -r --argjson i "$i" '
            .results[$i] |
            @sh "correct_answer=\(.correct_answer)",
            @sh "incorrect_answers+=(\(.incorrect_answers[]))"' "$data"
    )"

    printf 'The correct answer for question %d was "%s"\n' \
        "$((i+1))" "$correct_answer"
    echo 'The question had the following incorrect answers:'
    printf '\t"%s"\n' "${incorrect_answers[@]}"
done

Given the data in the question, this would output

The correct answer for question 1 was "Amazon"
The question had the following incorrect answers:
        "Netflix"
        "BBC"
        "CCTV"
The correct answer for question 2 was "60 lbs"
The question had the following incorrect answers:
        "30 lbs"
        "50 lbs"
        "70 lbs"
The correct answer for question 3 was "True"
The question had the following incorrect answers:
        "False"

In each iteration, the jq tool is used to pull out a specific element of the results array in the JSON document. It formats the data as a set of shell assignments. For example, when $i is 1, it would create the statements

correct_answer='60 lbs'
incorrect_answers+=('30 lbs')
incorrect_answers+=('50 lbs')
incorrect_answers+=('70 lbs')

The shell wouldthen evaluate these statements, which creates the variable correct_answer and the array incorrect_answers.

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .