Ask Ubuntu is a question and answer site for Ubuntu users and developers. Join them; it only takes a minute:

Sign up
Here's how it works:
  1. Anybody can ask a question
  2. Anybody can answer
  3. The best answers are voted up and rise to the top

Is there a decent way of implementing a quiz in Bash, where the questions are randomised each time and where the users' different responses (correct, wrong, pass) are recorded and then given at the end of the quiz?

I suppose arrays could be used to store the quiz questions and answers, and the quiz questions themselves could be asked within a while or until loop, and the various questions could be incremented until the last one was reached, then the user's performance could be assessed by using various conditional statements?

Please note that this old question discussed graphical quiz software and explictly mentioned sound/graphics, etc, while I am talking about only a command-line text-based quiz.

(I have answered my own question below...but if anyone has another framework for a quiz in Bash, please post.)

share|improve this question
I expect you already know of this, but why use Bash? Python, at least in my opinion, is much easier to use and more extensible. – RPi Awesomeness Oct 30 '13 at 17:41
@RPiAwesomeness Yes, Python is great with its more advanced data structures, but I also like messing about with Bash and trying various things. – user76204 Oct 30 '13 at 17:43
up vote 0 down vote accepted

The quiz framework I have come up with is posted below, and also at my my Github account.

As it looks rather complicated, I better give some explanation of how it works.

In the script, the Ubuntu versions and codenames of all the releases so far are stored in two arrays. The variables are then initialised outside of the until loop, so they can be incremented as necessary in the loop using C style (( var++ )) notation.

I then create another array for the question numbers, in this case for 19 questions I must include 0 - 18 inclusive:

questions_order=(15 4 1 10 8 3 13 0 11 16 2 7 5 17 6 9 14 18 12)

and then shuffle it using shuf and create a new array and use that for this particular run of the script:

shuffled_order=( $(shuf -n19 -e ${questions_order[@]}) )

This must be done outside of the loop, as I only want it executed once.

In short, the loop now runs until the current number of questions is equal to the "${target_questions}" variable, and then the results are given.

When a user gives a response to a question it will be interpreted by the various conditions in the case statement. Non answers (blanks), wrong answers, correct answers, and passes are all detected and receive responses, and the various variables are all incremented (e.g. for a wrong answer, (( wrong++ ))).

When the last question has been answered, the user's performance is evaluated where the number of right, wrong answers and passes (if any) are relayed and the time taken is also given.

This is a brief explanation, but the script itself has various comments that should make the workings clear(ish)! It could be adapted for any other type of quiz, say on capitals and countries.

When you have copied the script into a file, make it executable (chmod u+x), and run it with ./scriptname or place it in your ~/bin folder and call it by name like any other program, assuming your ~/bin is in PATH.

#!/usr/bin/env bash

# by mik, aka Exactus29,
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; either version 2 of the License, or
# (at your option) any later version.

# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# GNU General Public License for more details.

# You should have received a copy of the GNU General Public License
# along with this program. If not, see <>.


# Requires at least bash 3.1. As it makes extensive use of arrays, it would be a pain to 
# try to write it to be sh compatible, but it may be possible.

# arrays for questions and answers
codenames=("Warty Wharthog" "Hoary Hedgehog" "Breezy Badger" "Dapper Drake" "Edgy Eft" "Feisty Fawn" 
"Gutsy Gibbon" "Hardy Heron" "Intrepid Ibex" "Jaunty Jackalope" "Karmic Koala" "Lucid Lynx" "Maverick Meerkat" 
"Natty Narwahl" "Oneric Ocelot" "Precise Pangolin" "Quantal Quentzal" "Raring Ringtail" "Saucy Salamander")
versions=(4.10 5.04 5.10 6.06 6.10 7.04 7.10 8.04 8.10 9.04 9.10 10.04 10.10 
11.04 11.10 12.04 12.10 13.04 13.10)

# must intialize quest_index at -1 so we can use array index 0
# we start question counter at 0, so we can increment it to the value in number_questions
# the other variables below will all be incremented as necessary in the quiz

# declare questions_order array and then shuffle it and use that for this run of the script.
# must be declared outside of the loop, as only want it executed once
questions_order=(15 4 1 10 8 3 13 0 11 16 2 7 5 17 6 9 14 18 12)
shuffled_order=( $(shuf -n19 -e ${questions_order[@]}) )

printf "\nPress 0 to exit the quiz at any time.\n"
printf "You can ignore case in your answers, but correct spelling of the Ubuntu codenames is crucial.\n"
printf "Please enter the word pass if you don't know the answer.\n\n\n"


until (( questions == target_questions )); do

(( questions++ ))

(( quest_index++ ))

new_index=$( echo ${shuffled_order[$quest_index]})

# alternate style of questions, separate odd and even 

    if (( questions % 2 > 0 )); then 
        question_text="Ubuntu release had the development codename"
        question_text="was the Ubuntu development codename (both adjective and animal) for release"

read -p "(${questions}) What ${question_text} ${new_question}? " response 

# necessary to switch on nocasematch to cover if the answer is in capitals, as can't use normal [Mm] in case statement
shopt -s nocasematch

    case $response in 
          printf "Well done, correct answer. " 
          (( correct++ ))
          if (( questions < target_questions )); then 
              printf "Next question.\n\n" 
              printf "\nHowever, the quiz has now finished. Let us calculate your performance...\n"
              sleep 1
          printf "\nOk, time to finish with the quiz.\n"
         (( pass++ ))
         printf "Ok, you passed on this one..."
         if (( pass >= 10 )); then 
            printf "The passes are mounting, as you have now had ${pass} passes.\n"
         elif (( pass >= 2 )); then
            printf "Please try to give an answer, as you have now had ${pass} passes.\n" 

         if (( questions < target_questions )); then 
            printf "Let us try another question.\n\n"
            printf "\nHowever, the quiz has now finished. Let us calculate your performance.....\n"
            sleep 1

          if [[ -z ${response} ]]; then
              printf "Please provide an answer, as a blank answer is counted as a wrong answer; "
              (( no_response++ ))
              (( wrong++ ))
                if (( no_response == 1  )); then
                    printf "${no_response} blank response has been given so far. \n"
                elif (( no_response > 1 )); then
                    printf "${no_response} blank responses have been given so far. \n"   
              (( wrong++ ))
                if  (( questions % 2 > 0 )); then
                    printf "That is incorrect..."
                    printf "That is incorrect, ${response} was not the codename of ${new_question}. \n"

         if (( questions < target_questions )); then 
            printf "Let us try another question.\n\n"
            printf "\nHowever, the quiz has now finished. Let us calculate your performance.....\n"
            sleep 1


quiz_duration=$(( SECONDS - timer_begin ))
# could further process the $quiz_duration if it is over 60 seconds, and output the total in
# minutes and seconds using bc or awk

# checking against target_questions here, i.e. all of the questions
if (( questions == target_questions )); then 
    if (( correct == target_questions )); then
        printf "\nYou got them all right in ${quiz_duration} seconds, well done!\n\n"
    elif (( no_response == target_questions )); then 
        printf "\nYou gave ${no_response} blank responses, and so effectively gave ${no_response} wrong answers.\n\n"                
    elif (( wrong == target_questions )); then
        printf "\nYou got them all wrong in ${quiz_duration} seconds, oh dear!\n\n"
    elif (( pass == target_questions )); then 
        printf "\nYou passed on all the questions. Was this just a trial run?\n\n"             
        printf "\nOut of ${target_questions} questions, you got "
        # have to do this now because we have added the pass option, as you can have none wrong/correct,
        # some correct/wrong, and some passes
        (( wrong > 0 )) && printf "${wrong} wrong and "
        (( correct > 0 )) && printf "${correct} correct "

        if (( pass == 0 )); then
            printf "with no passess at all in a time of ${quiz_duration} seconds.\n "
        elif (( pass == 1 )); then 
            printf "with 1 pass in a time of ${quiz_duration} seconds.\n "
        elif (( pass > 1 )); then
            printf "with ${pass} passes in a time of ${quiz_duration} seconds.\n"


The quiz itself in action, with randomised questions:

enter image description here

share|improve this answer

Your Answer


By posting your answer, you agree to the privacy policy and terms of service.