LUX 211 - Shell Programming

Lesson 5: Chapter 10, Programming the Bourne Again Shell, part 1

Objectives:

This lesson begins the discussions from chapter 10. Objectives important to this lesson:

  1. Control structures
  2. File descriptors
  3. Positional parameters
  4. Special parameters
Concepts:
Chapter 10

Chapter 10 begins with some lessons on writing good programs by using control structures, which mainly deal with selection and iteration. The first example is an if...then structure, which you have used before, but this time we will consider what can go wrong when a user is asked to cooperate with one.

The author proposes that we should become accustomed to using the test command to run the conditional tests in any control structure. He shows us an example on page 420 that he continues to develop in other examples. Note the new syntax he uses first with the read command:

read -p "word 1: " word1

This is a new way to use the command.

  • In this case read is used with the -p option.
  • This means that the next phrase is a prompt for the user. It removes the need to use echo to prompt the user.
  • The prompt string is followed by the name of the variable whose new value we are asking the computer to read from the user's entry.

The next line in the example prompts and reads another variable. Then, the author shows us one way to use the test command:

if test "$word1" = "$word2"

Note that this line begins an if...then structure. It ends with the reserved word fi, which closes that structure.

  • Instead of placing a conditional phrase inside square brackets, we see the word test, followed by a space, followed by our actual conditional test statement.
  • The two values we are testing are enclosed in quotation marks, which act to limit the test to the values held in the variables, without including spaces in the test.
  • There are spaces on both sides of the equal sign, marking this clearly as a conditional statement, not an assignment statement.

The example ends a bit too quickly. The if...then structure runs one test, takes one action if it is true, and takes no action if it is false. Then the program ends. What if the user had not entered two arguments when asked to do so? What if the aguments were unacceptable?

The text continues the lesson with an example that checks the number of arguments passed to a script when it is called. This could be used as the first part of any script that will fail unless a proper number of arguments are passed on the command line. The text reminds us that if our logic has to know how many arguments were on the command line, that total is accessible as $#.

if test $# -eq 0

In the line above (There is no such thing as an "above line", children. Resist nonsense!), the conditional phrase tests whether the argument count is equal to 0. Note that no quotation marks were used. This is a comparison of two numbers, so spaces are not involved in the test. The syntax of the test command, however, still requires a space on either side of the -eq operator, as it does for all of its comparison operators. The -eq operator is required when testing the equality of numbers, as opposed to the = operator, which is used to compare strings. (I don't make this stuff up, I just explain it.)

In this example the author gives the user an error message if the argument count is not acceptable. That message is followed by one more line of code before closing the the if..then structure.

exit 1

This command stops running the the chkargs script at that point, before it sees the fi at the end of the structure. The author exited the script with a "return value" of 1 (false). Typically, we would use 1 to mean an abnormal exit, an error condition, or an undesireable state. If the input had passed the test, we might use "exit 0" as the last line of the test script, to indicate success or true. Using a return value of a script or a function allows us to run that function or script as a test condition that determines what happens next.

The text explores the if...then...else structure which we have seen before, and a new variation, if... then...elif..., which I am writing with an elipsis at the end for a reason.

  • If...then...else presents one test and two alternatives. This is sufficient in some situations. Note that the structure ends with one fi.
  • If...then...elif... presents a new test after each elif, and there can be any number of them. elif is a contraction of "else if". Regardless of the number of elif statements, the structure ends with one fi. The thing that determines the number of fi statemetns is the number of else statements. There should always be at least one. See the example on page 426.

The text continues this theme for a few pages, but we will skip over them, moving on to page 430, where we see a troubleshooting technique. One reason the text has not mentioned, which may lead you to start a new shell when you run a script, is the -x option for bash. If you start the bash shell with this option when calling a script, your script will be executed, line by line, but it will also display each line of the script on your screen before that line is executed, which may help you find a line at which the shell does not understand what you want it to do.

The next page begins a discussion of loop control structures that goes on for twenty pages. It begins with:

  • for...in - This is the name of the structure, but not the shape of it. The text shows us a general shape that looks like this:

    for index_variable in list of arguments
    do
    commands
    done

    The for operator reads the first value in the list of arguments, and assigns it to the index_variable. The commands are then processed, substituing the current value of index_variable wherever it occurs in the commands. This process repeats for every argument in the list of arguments. When the last argument has been assigned and processed through the commands, the loop is finished. This kind of loop runs a specific number of times, which is determined by the number of arguments in the list.
  • for - The text explains that this structure is the same as for...in with one difference. The for...in structure gets values for its index from a list of arguments. The for structure looks to the command line that called it, expecting to find a list of arguments there. The text is perhaps a bit confusing when it explains that we could make a for structure into a for...in structure by writing it like this:

    for arg in "$@"
    do
    commands
    done

    instead of like this:

    for arg
    do
    commands
    done

    The two structures mean the same thing, but the first one is easier to understand if you know that $@ stands for the list of arguments on a command line. The for structure is useful when you want a user to supply the list of arguments for the loop. The for...in is useful when the programmer can supply those values. We might observe that the for structure is more concise, but impossible to understand unless someone explains it to you.
  • while - The text provides a wordy explanation that the while structure is controlled by a test condition at the top of the structure. This means that the condition is tested before each loop is run, and the program stops entering new loops when the condition fails, even if that is the first time. In this way, the number of iterations can be controlled by something we are testing, not by the user, and not by the programmer. The structure of a while loop is shown on pages 435 and 436.
  • until - The text explains that the until loop is like the while loop, but its test logic is reversed. A while loop runs as long as its test is true. An until loop runs as long as its test is false. This is true for the examples in the text, but we could reverse that idea by using a NOT operator with the conditional test in either kind of loop. Looping secret: you can generally rewrite any loop as another kind of loop, mainly by changing the test condition.
  • break - This is not a kind of loop. It is a key word that tells the shell to stop running a loop. Use this work as you would use exit to break out of a script. The difference here is that break does not terminate the script, just the loop it is found in.
  • continue - This is another key word. It works like break, in that if it is encountered, it tells the shell to stop running the current iteration of a loop, then continue with the next iteration. To stop running a loop entirely, use break. To stop an iteration and go to the next one, use continue. The example on page 441 illustrates both behaviors.
  • case - A case structure is like a series of if...then...elif structures. The structure needs to receive a value that is matched against a different pattern in each substructure until a match is found, or it reaches the last substructure which is always an "anything else" kind of pattern. In bash, use the asterisk for the catch all pattern, which will match anything. This structure is often best when asking the user to choose something from a menu that prompts them to enter a single letter or numeral to represent their choice. An example of a menu script is shown on pages 444 through 448.
  • select - The select command has a built-in menu function that builds a numbered menu from a list of arguments. The command line that does this must be followed by a do...done structure that contains an if...then...elif structure that holds a component for each item in the menu. This does not make the construction of such a structure very automatic except in the case of the displayed menu. The text cautions that each segment of the if...then...elif structure (except the last one!) must end with the continue command if you want the menu to continue to be presented to the user. The last segment should be the choice to quit the menu, so it should contain the break command. Examine the model structure that appears on page 449.

I think that is enough for the first objective, so let's move on to page 452 and file descriptors. Each process that opens a file (and Linux thinks everything is a file) must keep track of those files. This is done by assigning a number to each opened file. The number is called a file descriptor. You might think that some processes would not need to open files, but the text tells us that any process will open at least three. They are STDIN (0), STDOUT (1), and STDERR (2). We have seen these numbers before, we just didn't call them file descriptors. The next few pages are pretty esoteric (and boring), so we will move on again to page 458.

The text discusses two kinds of parameters. The first type is positional parameters. Theses are the tokens that appear on the command line that calls a script.

  • As you know, the first is always the name of the script, found in $0.
  • The arguments passed to the script are numbered, and they are accessed by $1, $2, and so on through $9. The text chooses now to reveal, as I have hinted, that you can pass more than nine arguments to a script. After the ninth parameter, you have to enclose the number in square brackets, such as $[10] and $[11], to access them in script commands.
  • $# holds the number of arguments passed to the script, not counting the name of the script. (The text lists this one under Special Parameters.)
  • As noted above, $@ references the entire command string that called the script, but it makes each parameter available separately.
  • $* refers to all the positional parameters as one string.
  • The text cautions us to always use double quotation marks around a reference to a positional parameter. If you don't, you may find that the reference does not work. This is illustrated on page 459.
  • set - The text mentions that you cannot assign values to the positional parameter references with an equal sign, but you can assign values to them with the set command. Page 461 has an example of how this may be done, but the purpose and utility of doing so elude me.
  • shift - This command discards the first numbered positional parameter (referenced by $1), then renumbers the remaining ones. It is like discarding the first agument on the command line, then moving the rest one position to the left. If you want to discard more than the first one, you can give an integer argument to shift when it is called, which will discard that many arguments before renumbering the rest.

The text begins a list of Special Parameters on page 466. The first one is $#, which is discussed above.

  • $$ - This is a reference to the PID of the current process, so its value depends on what process is running and what ID was assigned when it started.
  • $! - This is a reference to the most recent process that was run in the background. Not a lot of competition for that one.
  • $? - This is a reference to the exit code (true=0=success, false=1 or another number=failure) of the most recently ended process, usually the command just given.

There are two more in this list, but this is getting tedious. Let's pause, study, do some assignments, and start on page 467 next time.