Creating a Magic Eightball in Bash

This little guided project is a fun way to get a sample of Bash programming. It can be used to learn everything for making a more involved text adventure later.

But First …

💡 This activity can also be done using Bash on REPL.it for those without access to a real Linux system. REPL.it also allows you to embed your game or script into your web site.

Step by Step

Change into your codebook project repo.

Create a bash directory if you have not yet done so.

cd 
cd repos/codebook
mkdir bash
cd bash

Now we need to make a file and convert it into an executable script.

touch eightball
chmod +x eightball

You can check the permissions.

ls -l eightball

Remember chmod changes the permissions mode of a file. The +x adds executable permission to the owner, group, and world. Without the x permissions the operating system just thinks its another plain file and not a runnable script.

-rwxr-xr-x 1 rob rob 0 May  2 20:31 eightball

Let’s put something in it to run just to test it out.

Open it with your editor of choice and add the following.

echo Welcome to the Magic Eightball

Save and close the file.

Now run it.

./eightball

The ./ means right in this here directory. The ./ is required to tell the operating system where your runnable script is. By default it only runs stuff in your $PATH, (which you can see with echo $PATH if you like).

If you see the following it means everything is working as expected.

Welcome to the Magic Eightball

At this point it is worth discussing something called the shebang line even though we don’t need one because the shell we are using for our terminal is already Bash.

If for no other reason than to get the proper syntax highlighting, let’s add a proper bash shebang line.

Open your script with your editor again (I won’t be saying that any more) and add the following to the very first line in the file.

#/bin/bash

Your bash might be someplace else, but then you are not using Linux (and are probably on a Mac). Just make sure it points to your bash. If you are on REPL.it technically you don’t need this, but add it anyway to remind you.

[“Why not use #!/usr/bin/env?”]

Now let’s put everything in a procedure (also called a function so we can use from something else if we want to later. Also call the procedure so it runs.

💬 Remember the difference between a procedure and a function is that the procedure (also called a subroutine describes the steps while a function returns a value and is self-contained. Procedures affect things outside of themselves, functions do not. These terms get interchanged all the time. People frequently mean procedure when they say function and, in fact, the languages have decided to use function, func, fn rather than distinguish them like earlier languages did. But you know the difference.

Starting with a procedure / function means we can move all of it into something else easily later.

#/bin/bash

eightball () {
  echo "Welcome to the Magic Eightball"  
}

eightball

⚠️ Don’t forget to indent the echo line and everything inside of the curly brackets. It’s good style.

💡 Now would be a great time to split your terminal with TMUX so you can run your script from one pane while editing it in another.

Next let’s get some input from the user. We will use read for this.

eightball () {
  echo "Welcome to the Magic Eightball"  
  echo
  read -p "Q: " question
}

Let’s use the Q: prompt for users’ question.

Let’s go ahead and add another echo just for a blank line.

Now we need the answers. Let’s declare an array and assign it some answers. We’ll make the answers have spaces in them just to show that you need quotes around them.

Notice there are no commas separating the answers, just one to a line. This is different from other languages you might learn — usually array lists values are separated with commas.

Make sure you declare and assign answers before all of the other code or it won’t know about it later.

Let’s also demonstrate scope by adding echo inside eightball $answers inside the procedure and another echo outside eightball $answers to see that $answers only lives inside the procedure.

eightball () {
  declare -a answers
  answers=(
    "Yes sir."
    "No way."
    "Maybe so."
  )
  echo inside eightball $answers
  echo "Welcome to the Magic Eightball"  
  echo
  read -p "Q: " question
}

echo outside eitghball $answers

⚠️ Remember you cannot have spaces around an = in Bash assignment.

💬 The declare is often left out of Bash scripts, but now with Bash 4 requiring it for associative arrays and allowing read-only variables as well as forcing them to be numbers it makes a lot of sense to always use declare. declare also covers what local does so might as well just use declare for everything. It’s just safer.

Notice the echo output showing that $answers only lives inside of eightball.

Go ahead and delete the two echo lines.

Now let’s echo the first answer.

eightball () {
  declare -a answers
  answers=(
    "Yes sir."
    "No way."
    "Maybe so."
  )
  echo "Welcome to the Magic Eightball"  
  echo
  read -p "Q: " question
  echo ${answers[0]}
}

That should always answer the first item from your answers list.

The number 0 inside ${answers[0]} says we only want the first one. Arrays — as with most languages — begin their index at 0 (not 1).

Now we need something RANDOM. Thankfully Bash has this built in for us to use. Try the following on the command line:

echo $RANDOM
echo $RANDOM
echo $RANDOM
27744
16038
18512

Notice that the number is always between 0 and 32767.

Now we can use the modulo operator to give us a random number.

echo $(( RANDOM % 3 ))
echo $(( RANDOM % 3 ))
echo $(( RANDOM % 3 ))
2
1
0

This gives us a completely random number. One of 0, 1, 2 but never 3.

We will use this to pick a random answer by combining it with the number of answers in our answers array.

TODO