Chapter 5. And Away We Go

Based on the discussions in Chapter 3, “What Is the Shell?,” you should realize that whenever you type something like

who | wc -l

that you are actually programming in the shell! That's because the shell is interpreting the command line, recognizing the pipe symbol, connecting the output of the first command to the input of the second, and initiating execution of both commands.

In this chapter, you'll learn how to write your own commands and how to use shell variables.

Command Files

A shell program can be typed directly at the terminal, as in

$ who | wc -l

or it can be first typed into a file and then the file can be executed by the shell. For example, suppose that you need to find out the number of logged-in users several times throughout the day. It's not unreasonable to type in the preceding pipeline each time you want the information, but for the sake of example, let's type this pipeline into a file. We'll call the file nu (for number of users), and its contents will be just the pipeline shown previously:

$ cat nu
who | wc –l
$

To execute the commands contained inside the file nu, all you now have to do is type nu as the command name to the shell: [1]

$ nu
sh: nu: cannot execute
$

Oops! We forgot to mention one thing. Before you can execute a program this way, you must change the file's permissions and make it executable. This is done with the change mode command chmod. To add execute permission to the file nu, you simply type

chmod +x file(s)

The +x says make the file(s) that follow executable. The shell requires that a file be both readable and executable by you before you can execute it.

$ ls -l nu
-rw-rw-r--    1 steve   steve     12 Jul 10 11:42 nu
$ chmod +x nu           Make it executable
$ ls -l nu
-rwxrwxr-x    1 steve   steve     12 Jul 10 11:42 nu
$

Now that you've made it executable, try it again:

$ nu
      8
$

This time it worked.

You can put any commands at all inside a file, make the file executable, and then execute its contents simply by typing its name to the shell. It's that simple and that powerful.

The standard shell mechanisms such as I/O redirection and pipes can be used on your own programs as well:

$ nu > tally
$ cat tally
      8
$

Suppose that you're working on a proposal called sys.caps and that the following command sequence is needed every time you want to print a new copy of the proposal:

tbl sys.caps | nroff -mm –Tlp | lp

Once again, you can save yourself some typing by simply placing this command sequence into a file—let's call it run—making it executable, and then just typing the name run whenever you want to get a new copy of the proposal:

$ cat run
tbl sys.caps | nroff -mm -Tlp | lp
$ chmod +x run
$ run
request id is laser1-15 (standard input)
$

(The request id message is issued by the lp command.) For the next example, suppose that you want to write a shell program called stats that prints the date and time, the number of users logged in, and your current working directory. You know that the three command sequences you need to use to get this information are date, who | wc -l, and pwd:

$ cat stats
date
who | wc –l
pwd
$ chmod +x stats
$ stats                        Try it out
Wed Jul 10 11:55:50 EDT 2002
     13
/users/steve/documents/proposals
$

You can add some echo commands to stats to make the output a bit more informative:

$ cat stats
echo The current date and time is:
date
echo
echo The number of users on the system is:
who | wc –l
echo
echo Your current working directory is:
pwd
$ stats                 Execute it
The current date and time is:
Wed Jul 10 12:00:27 EDT 2002

The number of users on the system is:
     13

Your current working directory is:
/users/steve/documents/proposals
$

Recall that echo without any arguments simply skips a line in the display. Shortly, you'll see how to have the message and the data displayed on the same line, like this:

The current date and time is: Wed Jul 10 12:00:27 EDT 2002

Comments

The shell programming language would not be complete without a comment statement. A comment is a way for you to insert remarks or comments inside the program that otherwise have no effect on its execution.

Whenever the shell encounters the special character # at the start of a word, it takes whatever characters follow the # to the end of the line as comments and simply ignores them. [2] If the # starts the line, the entire line is treated as a comment by the shell. Here are examples of valid comments:

# Here is an entire commentary line
who | wc –l        # count the number of users

#
#  Test to see if the correct arguments were supplied
#

Comments are useful for documenting commands or sequences of commands whose purposes may not be obvious or are sufficiently complex so that if you were to look at the program again in a week you might forget why they're there or what they do. Judicious use of comments can help make shell programs easier to debug and to maintain—both by you and by someone else who may have to support your programs.

Let's go back to the stats program and insert some comments:

$ cat stats
#
# stats -- prints: date, number of users logged on,
#          and current working directory
#

echo The current date and time is:
date

echo
echo The number of users on the system is:
who | wc -l

echo
echo Your current working directory is:
pwd
$

The extra blank lines cost little in terms of program space yet add much in terms of program readability. They're simply ignored by the shell.

Variables

Like virtually all programming languages, the shell allows you to store values into variables. A shell variable begins with an alphabetic or underscore (_) character and is followed by zero or more alphanumeric or underscore characters.

To store a value inside a shell variable, you simply write the name of the variable, followed immediately by the equals sign =, followed immediately by the value you want to store in the variable:

variable=value

For example, to assign the value 1 to the shell variable count, you simply write

count=1

and to assign the value /users/steve/bin to the shell variable my_bin, you simply write

my_bin=/users/steve/bin

A few important points here. First, spaces are not permitted on either side of the equals sign. Keep that in mind, especially if you're in the good programming habit of inserting spaces around operators. In the shell language, you can't put those spaces in.

Second, unlike most other programming languages, the shell has no concept whatsoever of data types. Whenever you assign a value to a shell variable, no matter what it is, the shell simply interprets that value as a string of characters. So when you assigned 1 to the variable count previously, the shell simply stored the character 1 inside the variable count, making no observation whatsoever that an integer value was being stored in the variable.

If you're used to programming in a language such as C or Pascal, where all variables must be declared, you're in for another readjustment. Because the shell has no concept of data types, variables are not declared before they're used; they're simply assigned values when you want to use them.

As you'll see later in this chapter, the shell does support integer operations on shell variables that contain strings that are also valid numbers through special built-in operations.

Because the shell is an interpretive language, you can assign values to variables directly at your terminal:

$ count=1                          Assign character 1 to count
$ my_bin=/users/steve/bin     Assign /users/steve/bin to my_bin
$

So now that you know how to assign values to variables, what good is it? Glad you asked.

Displaying the Values of Variables

The echo command is used to display the value stored inside a shell variable. To do this, you simply write

echo $variable

The $ character is a special character to the shell. If a valid variable name follows the $, the shell takes this as an indication that the value stored inside that variable is to be substituted at that point. So, when you type

echo $count

the shell replaces $count with the value stored there; then it executes the echo command:

$ echo $count
1
$

Remember, the shell performs variable substitution before it executes the command (see Figure 5.1).

echo $count.

Figure 5.1. echo $count.

You can have the value of more than one variable substituted at a time:

$ echo $my_bin
/users/steve/bin
$ echo $my_bin $count
/users/steve/bin 1
$

In the second example, the shell substitutes the value of my_bin and count and then executes the echo command (see Figure 5.2).

—echo $my_bin $count.

Figure 5.2. —echo $my_bin $count.

The values of variables can be used anywhere on the command line, as the next examples illustrate:

$ ls $my_bin
mon
nu
testx
$ pwd                          Where are we?
/users/steve/documents/memos
$ cd $my_bin            Change to my bin directory
$ pwd
/users/steve/bin
$ number=99
$ echo There are $number bottles of beer on the wall
There are 99 bottles of beer on the wall
$

Here are some more examples:

$ command=sort
$ $command names
Charlie
Emanuel
Fred
Lucy
Ralph
Tony
Tony
$ command=wc
$ option=-l
$ file=names
$ $command $option $file
      7 names
$

So you see, even the name of a command can be stored inside a variable. Because the shell performs its substitution before determining the name of the program to execute and its arguments, it scans the line

$command $option $file

and turns it into

wc -l names

Then it executes wc, passing the two arguments -l and names.

Variables can even be assigned to other variables, as shown in the next example:

$ value1=10
$ value2=value1
$ echo $value2
value1                                                     Didn't do that right
$ value2=$value1
$ echo $value2
10                                                             That's better
$

Remember that a dollar sign must always be placed before the variable name whenever you want to use the value stored in that variable.

The Null Value

What do you think happens when you try to display the value of a variable that has no value assigned to it? Try it and see:

$ echo $nosuch            Never assigned it a value

$

You don't get an error message. Did the echo command display anything at all? Let's see whether we can more precisely determine that:

$ echo :$nosuch:        Surround its value with colons
::
$

So you see no characters were substituted by the shell for the value of nosuch.

A variable that contains no value is said to contain the null value. It is the default case for variables that you never store values in. When the shell performs its variable substitution, any values that are null are completely removed from the command line, without a trace:

$ wc  $nosuch -l $nosuch $nosuch names
      7 names
$

The shell scans the command line substituting the null value for the variable nosuch. After the scan is completed, the line effectively looks like this:

wc -l names

which explains why it works.

Sometimes you may want to explicitly set a variable null in a program. This can be done by simply assigning no value to the variable, as in

dataflag=

Alternatively, you can list two adjacent pairs of quotes after the =. So

dataflag=""

and

dataflag=''

both have the same effect of assigning the null value to dataflag. Be advised that the assignment

dataflag=" "

is not equivalent to the three previous ones because it assigns a single space character to dataflag; that's different from assigning no characters to it.

Filename Substitution and Variables

Here's a puzzle for you: If you type

x=*

will the shell store the character * into the variable x, or will it store the names of all the files in your current directory into the variable x? Let's try it out and see:

$ ls                    What files do we have?
addresses
intro
lotsaspaces
names
nu
numbers
phonebook
stat
$ x=*
$ echo $x
addresses intro lotsaspaces names nu numbers phonebook stat
$

There's a lot to be learned from this small example. Was the list of files stored into the variable x when

x=*

was executed, or did the shell do the substitution when

echo $x

was executed?

The answer is that the shell does not perform filename substitution when assigning values to variables. Therefore,

x=*

assigns the single character * to x. This means that the shell did the filename substitution when executing the echo command. In fact, the precise sequence of steps that occurred when

echo $x

was executed is as follows:

  1. The shell scanned the line, substituting * as the value of x.

  2. The shell rescanned the line, encountered the *, and then substituted the names of all files in the current directory.

  3. The shell initiated execution of echo, passing it the file list as arguments (see Figure 5.3).

    echo $x.

    Figure 5.3. echo $x.

This order of evaluation is important. Remember, first the shell does variable substitution, then does filename substitution, and then parses the line into arguments.

The ${variable} Construct

Suppose that you have the name of a file stored in the variable filename. If you wanted to rename that file so that the new name was the same as the old, except with an X added to the end, your first impulse would be to type

mv $filename $filenameX

When the shell scans this command line, it substitutes the value of the variable filename and also the value of the variable filenameX. The shell thinks filenameX is the full name of the variable because it's composed entirely of valid variable name characters. To avoid this problem, you can delimit the end of the variable name by enclosing the entire name (but not the leading dollar sign) in a pair of curly braces, as in

${filename}X

This removes the ambiguity, and the mv command then works as desired:

mv $filename ${filename}X

Remember that the braces are necessary only if the last character of the variable name is followed by an alphanumeric character or an underscore.

Built-in Integer Arithmetic

The POSIX standard shell provides a mechanism for performing integer arithmetic on shell variables called arithmetic expansion. Note that some older shells do not support this feature.

The format for arithmetic expansion is

$((expression))

where expression is an arithmetic expression using shell variables and operators. Valid shell variables are those that contain numeric values (leading and trailing whitespace is allowed). Valid operators are taken from the C programming language and are listed in Appendix A, “Shell Summary.”

The result of computing expression is substituted on the command line. For example,

echo $((i+1))

adds one to the value in the shell variable i and prints the result. Notice that the variable i doesn't have to be preceded by a dollar sign. That's because the shell knows that the only valid items that can appear in arithmetic expansions are operators, numbers, and variables. If the variable is not defined or contains a NULL string, its value is assumed to be zero. So if we have not assigned any value yet to the variable a, we can still use it in an integer expression:

$ echo $a               Variable a not set
$
$ echo $((a = a + 1))   Equivalent to a = 0 + 1
1
$ echo $a
1                                                          Now a contains 1
$

Note that assignment is a valid operator, and the value of the assignment is substituted in the second echo command in the preceding example.

Parentheses may be used freely inside expressions to force grouping, as in

echo $((i = (i + 10) * j))

If you want to perform an assignment without echo or some other command, you can move the assignment before the arithmetic expansion.

So to multiply the variable i by 5 and assign the result back to i you can write

i=$(( i * 5 ))

Note that spaces are optional inside the double parentheses, but are not allowed when the assignment is outside them.

Finally, to test to see whether i is greater than or equal to 0 and less than or equal to 100, you can write

result=$(( i >= 0  &&  i <= 100 ))

which assigns result 1 if the expression is true and 0 if it's false:

$ i=$(( 100 * 200 / 10 ))
$ j=$(( i < 1000 ))        If i is < 1000, set j = 0; otherwise 1
$ echo $i $j
2000 0                         i                                 is 2000, so j was set to 0
$

That concludes our introduction to writing commands and using variables. The next chapter goes into detail on the quoting mechanisms in the shell.

Exercises

1:

Which of the following are valid variable names?

XxXxXx

_

12345

HOMEDIR

file.name

_date

file_name

x0-9

file1

Slimit

2:

Suppose that your HOME directory is /users/steve and that you have subdirectories as shown in the following figure:

Exercises

Assuming that you just logged in to the system and executed the following commands:

$ docs=/users/steve/documents
$ let=$docs/letters
$ prop=$docs/proposals
$

write the commands in terms of these variables to

  1. List the contents of the documents directory.

  2. Copy all files from the letters directory to the proposals directory.

  3. Move all files whose names contain a capital letter from the letters directory to the current directory.

  4. Count the number of files in the memos directory.

What would be the effect of the following commands?

  1. ls $let/..

  2. cat $prop/sys.A >> $let/no.JSK

  3. echo $let/*

  4. cp $let/no.JSK $progs

  5. cd $prop

3:

Write a program called nf to display the number of files in your current directory. Type in the program and test it out.

4:

Write a program called whos to display a sorted list of the logged-in users. Just display the usernames and no other information. Type in the program and test it out.



[1] Note that the error produced here varies between different shells.

[2] Note that the # may be your default erase character. If so, to enter the character into an editor such as ed or vi, you'll have to “escape” it by preceding it with a . Alternatively, you can change your erase character to something else with the stty command.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
3.133.133.117