LUX 211 - Shell Programming

Lesson 6: Chapter 10, Programming the Bourne Again Shell, part 2

Objectives:

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

  1. Variables
  2. Environment variables and inheritance
  3. Array variables
  4. Builtin commands
  5. Expressions
  6. Recursive scripts
Concepts:
Chapter 10

Page 467 begins several pages that discuss types of variables. This should be mostly review:

  • shell variables - The text explains that a variable is typically visible only to the shell in which it is created. It is not available to subshells, or to other shells you might invoke later. In the example in the text, the author creates a variable x, and initializes it to 5. He then starts a new shell, tries to echo the value of x, and the command fails. The author is now in a child shell of the shell where x is known. The child shell does not know about shell variables that exist in its parent shell.
  • environment variables - When we export a variable, we make it available in the environment, the space in which all shells exist. It can be accessed by a miracle in the next bullet point.
  • passing the environment - When Linux creates a new shell, it passes a copy of the current environment (it can change) to the new/child shell. That is fine, as long as you use the environment variables with no intention of changing them. If you change an environment variable in the child shell, that change will be in effect for that shell, but it has no effect on the same variable in the parent shell, which you will return to when the child shell terminates.

    The text explains this point with regard to inheritance. When a process calls another process, the first one is a parent, and the second one is a child. The child inherits the environment in the state that it is in when the child is born. Any changes the child makes to the environment will be passed to any child it creates. In this way, each child will receive its parent's environment, but with any changes the parent made (or that were made around it). In this respect, inheritance only flows down, never up.
  • inheritance - The text proves the statements above in an example on pages 469 and 470. It is a little hard to follow, so let me try.

    You need to know what is in two scripts, and what happens on each line.
    script
    command
    result
    extest2
    export cheese=american
    Create an exported variable called cheese, set equal to American

    echo "$0 1: $cheese"
    extest2 1: American

    ./subtest
    Calls the subtest script. It inherits the cheese variable and its value.
    subtest
    echo "$0 1: $cheese" subtest 1: American

    cheese=swiss
    changes value of the inherited variable to Swiss

    echo "$0 2: $cheese" subtest 2: Swiss
    extest2 echo "$0 2: $cheese" The subtest script ends, control returns to next line in extest2 script:
    extest2 2: American

What happened?

  • The first script runs. It creates, initializes, and exports a variable. It echoes the value of the variable. It calls the second script, now the child of the first script.
  • The second script runs. It echoes the value of the variable, which it inherited. The thing is, this script inherited its own version of the variable. It changes the value of the variable, echoes that new value to the screen, and stops running. We return to the line that called this script, back in the first script.
  • The first script echoes the value of the variable, which is unchanged. It is unchanged because it is not the same variable as the one that was changed in the second script. Inheritance does not flow upward. The first script gave a copy of its environment to the second script. The second script changed the copy, not the original. If we wanted to have them access the same variable, we would have to pass a pointer to the memory space of the original variable, and that is out of the scope of this lesson.

On page 472, the text explores a variable that has no value. If we declare or export a variable, it exists. However, if we do not assign a value to it, the shell typically assumes that the variable holds an empty string. That is pretty accurate, and it is is more intuitive than the three alternatives proposed by the text that either use or assign a default value, or assign an error message if the variable is empty. This concept is a little slipshod for my taste. If your variable needs a value, give it one, and change it when needed.

Page 474 bring us to the subject of arrays. The author spends just over a page on them, and does not make his points very clearly. Let me suggest that you should read this article on the subject on the Linux Journal web site. In Bash, an array is a series of memory locations that can be accessed together or individually. The article behind the link in the sentence above starts with an example.

arr=(Hello World)

This looks a lot like assigning some words to a variable. In this case, we did not use quotation marks. We used a set of parentheses to enclose the words. This makes arr the name of an array. We can access each member of the array by using the name of the array followed by square brackets enclosing a number that represents the order of the members.

arr[0]=Hello
arr[1]=World

This pair of commands would have accomplished the same thing as the previous example: arr is the name of an array, its first member, identified by index 0, is Hello, and its second member, identified by index 1, is World. Note that this method did not require the use of parentheses.

So, that is two methods to create an array and load some values into it. As the text and the article mention, it is not quite so easy to read what is in an array. Normally, you deal with one item in a array at a time. An example in the text shows how to use the proper notation with echo:

echo ${arrayname[index]}

Using the example array above, we could enter the command echo ${arr[0]} if we wanted to see the first item in that array, which is Hello.

Basic syntax rule: the index value is enclosed by square brackets. The entire reference to the array item (name and index) is enclosed by curly braces. A dollar sign is needed to the left of the left curly brace to read the value stored in the array item. The text makes a point of stating that you must use both the square brackets and the curly braces. If you stick with this notation and remember the first item in the array is at index [0], you should be fine when reading from and writing to the array items.

The text becomes difficult to understand for the rest of its discussion. Let's move ahead to the bottom of page 475, Functions often have variables in them, however, the point of this discussion is that a script that calls a function automatically gives that function access to all variables in the script. You may be filled with indignation at this idea, but it is so. The text explains that a function called from a script is operating in the same environment as that script, so it has access to the same environment variables, not copies of them. This solves the problem you may have seen in the discussion of inheritance. A script and the functions it calls can access the same variables, so they can be changed by either the script or by a function, and the change matters to the other one. This benefit, however, leads to the opposite problem we saw under inheritance.

Suppose a function is useful. Suppose it is popular and it is used by most of the programmers on our staff of many. We make sure that it is available in a startup file on our Linux workstations. Now suppose that the programmer who wrote that function and the next programmer to use it both used the same variable name in their respective work, This variable is changed in the script and changed in the function, in different counterproductive ways. Well, what do we do about that?

When we write a function, we may want to make sure that variables that are meant to have meaning only to the function are created with a modifier that limits their visibility. That modifier is local. The text offers an example of how this might be used:

  1. In the example in middle of page 476, a script assigns the value 10 to a variable called count.
  2. The script calls a function called count_down, passing it the value 4 as an argument.
  3. Inside the function, a variable called count is declared to be local: local count
  4. The local variable count is assigned the value of the first argument passed to the function, which was 4.
  5. The function runs a loop that displays the current value of count, and subtracts 1 from it each time the loop runs. The local variable count is eventually reduced to 0.
  6. The function ends, the script continues, and echoes the value of its count, which is still 10.

This way, what happens inside the function, like what happens in Vegas, stays inside the function. Well, maybe its even more so for the function. Anyway, its a better way to write your functions if there is a possibility of the function changing something it should not change.

The text brings up a term that it has used several times, but not explained too well. Builtin commands are commands that are part of the shell. Some commands are stored in separate files, typically stored in a bin folder somewhere. Builtin commands are not separate, so when you call one of them in the shell, a builtin command does not start a new process, it simply continues the process that is the shell. The text discusses six builtin commands (for fifteen pages!), then offers a list of more on pages 491 and 492. A point the text made previously is that if a command is a builtin, it will run before any command in a bin folder is run, making it hard to execute the binary version.

This is an interesting point, but not worth a lot to us. It is more valuable to know what some of these commands do, so look through this section of the chapter to learn about more commands you can use in scripts.

Page 492 returns to doing math in a shell script. We see three acceptable syntaxes for an equation:

  • let "value=value * 10 + new"
    Using let removes the need to use dollar signs on the right side of the equal sign, but it requires double quotes around the entire expression if you have used spaces in it.
  • ((value=value * 10 + new))
    The double parentheses are accepted as the same notation as a let command.
  • let value=value*10+new
    Using let without the double quotes is allowed if we remove the spaces in the expression

There is more on this page, but three ways of writing math statements are enough. The next two pages cover more examples of conditional testing, reminding us that a conditional test will evaluate to being true or false. Starting on page 495, there is a list of math operators that can be used with variables. The ++ and -- operators are handy in programs, and are worth some attention. First, be aware of the two operators. Then, worry about how their position affects what they do. Hang on, it isn't that bad.

  • ++ is the incrementation operator. When applied to a variable, it means to add 1 to the value the variable currently holds.
  • -- is the decrementation operator. When applied to a variable, it means to subtract 1 from the value the variable currently holds.
  • The text should have mentioned the two ideas above first, before confusing the issue with prefix and postfix notation.
  • Prefix notation: ++variable means to add 1 to the value of the variable, then use it in the context of the statement where it appears. This is sort of like incrementing a variable before a loop is run.
  • Postfix notation: variable++ means to use the variable in the context of the statement where it appears, then add 1 to the value of the variable. This is sort of like incrementing a variable after a loop is run.
  • The decrementation operator can be used in prefix and postfix notations as well.

The text continues with several familiar math operators. Look them over, and do a web search for any you don't know about. Note that some symbols are used for more than one function, so discuss what you are not sure about in class.

On page 499, the text reminds us that the && and || operators can stand for Boolean concepts (AND and OR, respectively) but they mean something else when they separate two commands.

  • command1 && command2
    The shell will attempt to run command1, and will only run command2 if the first command completes successfully.
  • command1 || command2
    The shell will attempt to run command1, and will only run command2 if the first command completes unsuccessfully.

The single pipe character takes the output from the process on the left and supplies it as input to the process on the right. This is still true if the pipe character is the last character on a line. In this case, the next command line is assumed as the process that is receiving the output of the process on the first line.

Turn to page 501 to see a pretty dry discussion of recursion, a method of doing something that requires your method to call itself as needed. The text mentions that it should be possible to rewrite any script requiring recursion with an appropriate loop. Take a look at the material and decide which makes more sense to you for the second assignment for this week.