By combining both methods, of course! Before we start executing the actual functionality of the script, we need to verify whether all necessary information has been supplied. If it has not, we can then prompt the user for the missing information.
We're going to look ahead slightly to Chapter 11, Conditional Testing and Scripting Loops, and explain the basic use of if-then logic. We'll combine this with the test command, which we can use to check if a variable contains a value or is empty. If that is the case, then we can prompt the user with read to supply the missing information.
At its heart, if-then logic is nothing more than saying if <something>, then do <something>. In our example, if the variable of character_name is empty, then use read to prompt for this information. We'll do this for all three parameters in our script.
Before we can explain the test command, we need to go back a little bit and explain exit codes. Basically, every program that runs and exits returns a code to the parent process that originally started it. Normally, if a process is done and execution was successful, it exits with code 0. If execution of the program was not successful, it exits with any other code; however, this is usually code 1. While there are conventions for exit codes, often you will just encounter 0 for good exits and 1 for bad exits.
When we use the test command, it generates exit codes conforming to the guidelines as well: if the test is successful, we see exit code 0. If it is not, we see another code (probably 1). You can see the exit code of the previous command with the echo $? command.
Let's look at an example:
reader@ubuntu:~/scripts/chapter_08$ cd
reader@ubuntu:~$ ls -l
total 8
-rw-rw-r-- 1 reader reader 0 Aug 19 11:54 emptyfile
drwxrwxr-x 4 reader reader 4096 Sep 1 09:51 scripts
-rwxrwxr-x 1 reader reader 23 Aug 19 11:54 textfile.txt
reader@ubuntu:~$ mkdir scripts
mkdir: cannot create directory ‘scripts’: File exists
reader@ubuntu:~$ echo $?
1
reader@ubuntu:~$ mkdir testdir
reader@ubuntu:~$ echo $?
0
reader@ubuntu:~$ rmdir testdir/
reader@ubuntu:~$ echo $?
0
reader@ubuntu:~$ rmdir scripts/
rmdir: failed to remove 'scripts/': Directory not empty
reader@ubuntu:~$ echo $?
1
A lot happened in the previous example. First, we tried to create a directory that was already present. Since we can't have two directories with the same name (in the same location), the mkdir command failed. When we printed the exit code using $?, we were returned 1.
Moving on, we successfully created a new directory, testdir. When we printed the exit code after that command, we saw the number for success: 0. After successfully removing the empty testdir, we saw an exit code of 0 again. When we tried to remove the not-empty scripts directory with rmdir (which isn't allowed), we got an error message and saw that the exit code was again 1.
Let's get back to test. What we need to do is verify whether a variable is empty. If it is, we want to start a read prompt to have it filled by user input. First we'll try this on the ${PATH} variable (which will never be empty), and then on the empty_variable, which will be indeed empty. To test whether a variable is empty, we use test -z <variable name>:
reader@ubuntu:~$ test -z ${PATH}
reader@ubuntu:~$ echo $?
1
reader@ubuntu:~$ test -z ${empty_variable}
reader@ubuntu:~$ echo $?
0
While this might seem like the wrong way around at first, think about it. We're testing whether a variable is empty. Since $PATH is not empty, the test fails and produces an exit code of 1. For ${empty_variable} (which we have never created), we are sure it is indeed empty, and an exit code of 0 confirms this.
If we want to combine the Bash if with test, we need to know that if expects a test that ends in an exit code of 0. So, if the test is successful, we can do something. This fits our example perfectly, since we're testing for empty variables. If you wanted to test it the other way around, you'd need to test for a non-zero length variable, which is the -n flag for test.
Let's look at the if syntax first. In essence, it looks like this: if <exit code 0>; then <do something>; fi. You can choose to have this on multiple lines, but using ; on a line terminates it as well. Let's see whether we can manipulate this for our needs:
reader@ubuntu:~$ if test -z ${PATH}; then read -p "Type something: " PATH; fi
reader@ubuntu:~$ if test -z ${empty_variable}; then read -p "Type something: " empty_variable; fi
Type something: Yay!
reader@ubuntu:~$ echo ${empty_variable}
Yay!
reader@ubuntu:~$ if test -z ${empty_variable}; then read -p "Type something: " empty_variable; fi
reader@ubuntu:~
First, we used our constructed if-then clause on the PATH variable. Since it is not empty, we did not expect a prompt: a good thing we did not get one! We used the same construct, but now with the empty_variable. Behold, since the test -z returned exit code 0, the then part of the if-then clause was executed and prompted us for a value. After inputting the value, we could echo it out. Running the if-then clause again did not give us the read prompt, because at that point the variable empty_variable was no longer empty!
Finally, let's incorporate this if-then logic into our new interactive-ultimate.sh script:
reader@ubuntu:~/scripts/chapter_08$ cp interactive.sh interactive-ultimate.sh
reader@ubuntu:~/scripts/chapter_08$ vim interactive-ultimate.sh
reader@ubuntu:~/scripts/chapter_08$ cat interactive-ultimate.sh
#!/bin/bash
#####################################
# Author: Sebastiaan Tammer
# Version: v1.0.0
# Date: 2018-09-09
# Description: Show the best of both worlds!
# Usage: ./interactive-ultimate.sh [fictional-character-name] [actual-
# location] [favorite-food]
#####################################
# Grab arguments.
character_name=$1
location=$2
food=$3
# Prompt the user for information, if it was not passed as arguments.
if test -z ${character_name}; then read -p "Name a fictional character: " character_name; fi
if test -z ${location}; then read -p "Name an actual location: " location; fi
if test -z ${food}; then read -p "What's your favorite food? " food; fi
# Compose the story.
echo "Recently, ${character_name} was seen in ${location} eating ${food}!"
reader@ubuntu:~/scripts/chapter_08$ bash interactive-ultimate.sh
"Goofy"
Name an actual location: Barcelona
What's your favorite food? a hotdog
Recently, Goofy was seen in Barcelona eating a hotdog!
Success! We were prompted for location and food, but character_name was successfully resolved from the argument that we passed. We've created a script that we can use both fully interactive, without supplying arguments, but also non-interactive with arguments.