0

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"
      ]
    }
  ]
}
  • So what do you want your bash array to contain? – jesse_b Nov 21 '19 at 20:37
  • @Jesse_b I want the bash array to include correct_answer and incorrect_answers. – Philip Kirkbride Nov 21 '19 at 20:38
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"
|improve this answer|||||
  • Try this with .results[1] - the quotes are literal. – Michael Homer Nov 21 '19 at 21:03
  • Oh yeah, that's much better than mine – glenn jackman Nov 21 '19 at 21:04
  • This would be perfect, I'm getting black when I echo though. – Philip Kirkbride Nov 21 '19 at 21:11
  • Are you sure you have valid contents in $quiz? – glenn jackman Nov 21 '19 at 21:12
  • Sorry I had deleted my quiz line. This works great thanks! – Philip Kirkbride Nov 21 '19 at 21:13
4

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
|improve this answer|||||
  • Are you expecting I will write the answers like "Netflix" every-time, because it will be random questions and answers each API call. – Philip Kirkbride Nov 21 '19 at 20:50
  • I'm not expecting anything, I'm responding to the information contained in your question. – glenn jackman Nov 21 '19 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. – Philip Kirkbride Nov 21 '19 at 20:53
  • Which you store in the quiz variable, which I use in my answer. – glenn jackman Nov 21 '19 at 20:54
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
|improve this answer|||||
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
  • This is awesome, beyond what I planned to do but I'll use this now! – Philip Kirkbride Nov 22 '19 at 1:13
  • 1
    Don't just use it. Understand it. Ask questions about bits you don't get. – glenn jackman Nov 22 '19 at 1:26

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.