LUX 211 - Shell Programming
Lesson 5: Chapter 10, Programming the Bourne Again Shell, part 1
This lesson begins the discussions from chapter 10. Objectives important
to this lesson:
- Control structures
- File descriptors
- Positional parameters
- Special parameters
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.
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
- 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:
in list of arguments
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:
instead of like this:
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
- 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.
- 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
- 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 $
and $, to access them in
- $# 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.