bourne shell programming-robert p sayle

102
Bourne_Shell_Programming-Robert_P_Sayle.html file:///Z:/VMWare_Shared/Bourne_Shell_Programming-Robert_P_Say... 1 de 102 11/10/2010 16:13 Bourne Shell Programming By Robert P. Sayle Author's Note My younger sister asked me a question a few days ago that has for ced me to reexamine my motivations for writing this book. She asked me, "What is a shell?" If I remember correctly, I did give her a five-and-dime answer, but I found that as I did so, I could not quite explain the entire concept satisfactorily. Since she posed this question, I have found the query creeping into my thou ghts. It has challenged me to recall this text's origins, what my original intentions for it were, and what I now believe to be its scope and purpose. In doing so, I now understand that there is a fairly simple answer to her question, which I will get to in the first chapter, but I have also realized that there is a rich variety to what can be called a shell and a subsequent history of computing to which this book and the shell are linked. This book derives from a class I produced and taught during my programmer days at ARINC. At that time, I wanted to share with my colleagues how to customize UNIX rather than fight it. I was frustrated watching a group of talented C++ programmers fumble around in such a rich operating system. I figured I'd write a notebook that could explain the simple programming facilities available in the shell, and I would never again have to watch a friend hit the up-arrow key or enter !! just to monitor their program. So I took a couple weeks in between projects and wrote and presented what can now be considered the outline for this book. The only problem was that I had peaked my peers' interest in the shell. Soon I was receiving phone calls and office visits that required a more in depth explanation than what I had written on a few acetate slides. By this time, two very important trends in computing and networking started. A couple years earlier, a few smart chaps out at the University of Illinois had developed the idea of hypertext documents, and text is all they were, that would allow users to move through them by selecting words that linked documents across different networks. By the time I had given my class, the idea had bloomed into HTML and a little known buzz word called the World Wide Web. Seeing how fun and easy it was to create interlinked documents, with pictures and tables nonetheless, I thought it would be a gre at idea to try translating the class notes into an active book. Imagine how useful it would be to a reader to move from chapter to chapter at the click of a button or to follow a link from a concept presented in the book's index immediately to the written text describing it. Besides that, I figured HTML was a good format for its operating system independence as well as its growing acceptance among the Internet community. About the same time I started converting the class notes into HTML, the Microsoft juggernaut began gaining steam with a new operating system, Windows95. The key to this operating system was and still is its eye catching GUI. It was a big step over Windows 3.1. Suddenly, Mic rosoft had produced an easy to use and stable desktop operating system that anyone could use. Of course I could now discuss the merits of Macintosh and how much Win95 looks and feels like Macintosh, but I digress. Microsoft marketted the hell out of its new baby, and Windows95 sold like no other program or operating system. Being both a UNIX and Macintosh biggot, I would not succumb to the marketing hype. I promised myself. I would not do it. Linux or die! Then I saw that a good friend of mine had installed Win95, and people at work began using it. The trade magazines carried more and more articles talking about the use of Windows95 in corporate America. Slowly, it was closing in on me. I finally fell prey to Windows when a laptop I had ordered came preconfigured with it. I have to admit that once I used it I was hooked. Pretty soon, I noticed a majority of users inside and outside my office were using Windows95.

Upload: crlx0123

Post on 10-Oct-2014

61 views

Category:

Documents


1 download

TRANSCRIPT

Bourne_Shell_Programming-Robert_P_Sayle.html file:///Z:/VMWare_Shared/Bourne_Shell_Programming-Robert_P_Say...

1 de 102 11/10/2010 16:13

Bourne Shell ProgrammingBy Robert P. Sayle

Author's NoteMy younger sister asked me a question a few days ago that has forced me to reexamine my motivations forwriting this book. She asked me, "What is a shell?" If I remember correctly, I did give her a five-and-dimeanswer, but I found that as I did so, I could not quite explain the entire concept satisfactorily. Since sheposed this question, I have found the query creeping into my thoughts. It has challenged me to recall thistext's origins, what my original intentions for it were, and what I now believe to be its scope and purpose. Indoing so, I now understand that there is a fairly simple answer to her question, which I will get to in the firstchapter, but I have also realized that there is a rich variety to what can be called a shell and a subsequenthistory of computing to which this book and the shell are linked.

This book derives from a class I produced and taught during my programmer days at ARINC. At that time,I wanted to share with my colleagues how to customize UNIX rather than fight it. I was frustrated watchinga group of talented C++ programmers fumble around in such a rich operating system. I figured I'd write anotebook that could explain the simple programming facilities available in the shell, and I would never againhave to watch a friend hit the up-arrow key or enter !! just to monitor their program. So I took a coupleweeks in between projects and wrote and presented what can now be considered the outline for this book. The only problem was that I had peaked my peers' interest in the shell. Soon I was receiving phone callsand office visits that required a more in depth explanation than what I had written on a few acetate slides.

By this time, two very important trends in computing and networking started. A couple years earlier, a fewsmart chaps out at the University of Illinois had developed the idea of hypertext documents, and text is allthey were, that would allow users to move through them by selecting words that linked documents acrossdifferent networks. By the time I had given my class, the idea had bloomed into HTML and a little knownbuzz word called the World Wide Web. Seeing how fun and easy it was to create interlinked documents,with pictures and tables nonetheless, I thought it would be a great idea to try translating the class notes intoan active book. Imagine how useful it would be to a reader to move from chapter to chapter at the click of abutton or to follow a link from a concept presented in the book's index immediately to the written textdescribing it. Besides that, I figured HTML was a good format for its operating system independence aswell as its growing acceptance among the Internet community.

About the same time I started converting the class notes into HTML, the Microsoft juggernaut began gainingsteam with a new operating system, Windows95. The key to this operating system was and still is its eyecatching GUI. It was a big step over Windows 3.1. Suddenly, Microsoft had produced an easy to use andstable desktop operating system that anyone could use. Of course I could now discuss the merits ofMacintosh and how much Win95 looks and feels like Macintosh, but I digress. Microsoft marketted the hellout of its new baby, and Windows95 sold like no other program or operating system.

Being both a UNIX and Macintosh biggot, I would not succumb to the marketing hype. I promised myself. I would not do it. Linux or die! Then I saw that a good friend of mine had installed Win95, and people atwork began using it. The trade magazines carried more and more articles talking about the use ofWindows95 in corporate America. Slowly, it was closing in on me. I finally fell prey to Windows when alaptop I had ordered came preconfigured with it. I have to admit that once I used it I was hooked. Prettysoon, I noticed a majority of users inside and outside my office were using Windows95.

Bourne_Shell_Programming-Robert_P_Sayle.html file:///Z:/VMWare_Shared/Bourne_Shell_Programming-Robert_P_Say...

2 de 102 11/10/2010 16:13

When Microsoft released Windows NT 4, which pretty much has the same interface as Win95, I knew it wasover for UNIX. It would not happen overnight of course, but why bother writing a book that will beobsolete within a few years. So I shelved the book and actually made a career change that would move meaway from daily programming into the design and implementation of data communication networks. Whileon this new path, I noticed the monthly toutings by various trade magazines of NT's market penetration. Charts and graphs that had been speculation of its deployment within a few years had become real. Andwhy not? It is much easier for an administrator to point and click through an operating system then to haveto learn a cryptic command line interface (CLI) like UNIX. Of course these same administrators also do notunderstand the flexibility and efficency of this CLI, but I digress again. Suffice it to say, NT had made itsmark and was here to stay after a few years of introduction into corporate America.

And then one day my sister asked me, "What is a shell?"

I was surprised. She had been telling me about her new job as a database programmer and how the productran on a couple variants of UNIX. She was telling me how she knew some UNIX from her previous job, butnow she would really need to understand it, and then the question

Since our discussion, I decided to take another look at NT and UNIX. I found reports of NT's shortcomingsas a secure and stable platform. Serious problems with its networking and administrative modules couldbring the operating system to an abrupt halt. Performace evaluations rated it poorly in, for example, itsability to act as a private gateway for remote access. I even had a couple customers tell me they would notuse NT for mission critical applications. Instead, they were still deploying UNIX based machines. Aboutall they were using NT for was for basic user accounting, file, and printing services. Still, I realize this isentirely a natural part of any operating system's life-cycle. NT will be an enterprise system someday, but asI write this book, it is not there. Hence, there is still a strong demand for UNIX.

So here I sit, busily typing with a renewed vigor to teach my sister and anyone else who cares to learn howto program the Bourne shell in order to use UNIX to its fullest potential.

Robert Sayle June 8, 1998

AcknowledgementsThanks to Kathleen for inspiring me to revisit this text and complete it.

Thanks to Mark for helping me clean up the interface.

Thanks to Brian for dotting my i's and crossing my t's, so to speak.

ConventionsThis document uses the following formatting conventions.

Plain text is written in a normal style font:The Bourne shell contains many built-in commands.

Commands and options entered by a user are displayed in a fixed width, bold font:

ls -lg /etc/raddb/users

Bourne_Shell_Programming-Robert_P_Sayle.html file:///Z:/VMWare_Shared/Bourne_Shell_Programming-Robert_P_Say...

3 de 102 11/10/2010 16:13

Responses and queries provided by the operating system are shown in a fixed width font:

/bin/ls: /etc/raddb/users file not found.

Table and diagram captions are given with an italic font:

Table 1.2-1: Quoting Mechanisms

About the BookBourne Shell Programmingteaches UNIX users how to harness the power of the shell. The book assumes that the reader has at least ageneral knowledge of UNIX including its commands, syntax, and operation. It also assumes that the readeralso understands simple programming techniques inherent to most programming languages. The book doesnot provide instuction on the basics of UNIX. It instead builds upon these basics by showing how tocombine them with the shell's programming facilities. The goal is to train users to employ these techniquesat both the command line and within scripts so that the operating system becomes a tool instead of ahindrance.

This book was written over the course of a number of years mainly because the author switched careerpaths. The change consequently resulted in a text tested against two different versions of UNIX. Someexamples are shown in Sun Solaris 2.4 while others are given from Linux Slackware 3.2. Readers arecautioned to check their local operating system's manual pages on any command demonstrated for propersyntax and operation.

The author realizes he is prone to error and respectfully requests any corrections be forwarded by email. Suggestions on improving this book are also welcome as are offers to publish it through traditional channels.

For notes on the format of this book, the reader is directed to the conventions listed in the book's foreword.

1. Shell BasicsWhat is a Shell?1.Shell Types2.Grouping Commands3.I/O4.Regular Expressions5.Quoting6.

1.1 What is a Shell?

Simply stated, a shell provides an interface to the operating system facilities including files, printing,hardware devices, and applications. This is a very broad definit ion indeed. It applies from something assimple as a telnet interface to a complex windowing system such as the Common Desktop Environment(CDE). It also transcends operating system brands. For example, a ksh session on an HP UNIX workstationis as much a shell as a DOS window is to a Microsoft Windows 95 PC or a DEC windows session is to aVAX/VMS desktop server. In each case, there exist methods for reading files, queuing print jobs, orlaunching programs. On a Windows 95 PC, a user might read a file by invoking the Notepad program from

Bourne_Shell_Programming-Robert_P_Sayle.html file:///Z:/VMWare_Shared/Bourne_Shell_Programming-Robert_P_Say...

4 de 102 11/10/2010 16:13

the Start Menu. An operator could use the PRINT command in VMS to view the current jobs queued for aline printer. A programmer might open a terminal window from the CDE control panel in order to begincompiling and linking a program. In each case, the interface gives the user some way to command theoperating system.

Another way to view a shell is to consider it in the context of a metaphor using its own name. Consider anegg shell. An egg shell is a hard, protective wrapper that encases a growing embryo. Needless to say,UNIX is hardly like a developing fetus, but the wrapper part fits. Figure 1.1-1 depicts the relationship. TheUNIX shell can be treated as an operating system wrapper. It encapsulates the devices, applications, files,and communications that in turn access the kernel to perform all of the functions expected of computers. Once again, this is a simplistic explanation of how a user interoperates with a computing system, but itshould help demystify the concept of a shell.

Figure 1.1-1. A Shell as an Operating System Wrapper

1.2 Shell Types

Naturally, this book deals with a very specific shell, namely, the UNIX Bourne shell. There are a number ofsimilar shells that are worth mentioning, but first, it is important to note that each of these shells shares thefollowing qualities:

Command line interface (CLI),Rigid syntax,High degree of flexibility,Programmable.

The traditional UNIX shells are command line driven. A user enters textual input consisting of operatingsystem commands with options and switches. The computer in turn provides textual output according to thecommand given followed by a prompt for the next command. The richness of an application's options combined with thebuilt-in interpreted programming language of the shell allows a user to easily command the operating systemto perform very specific functions. In a graphical environment, this same flexibility is lost in a programwhenever a user is forced to use check boxes, radio buttons, or selection lists. It is much more expedient toinclude all the options on one command line then it is to point-and-click through a pop-up window. On theother hand, this same flexibility loses the ease of use that GUIs provide. A GUI can very easily force a userto review all necessary options before running a program. A command line driven system requires the userto research the command thoroughly before issuing it. Still, the syntax maybe restrictive, but the fact thatthese same commands can be easily stored in an executable file and replayed at a later date once again point

Bourne_Shell_Programming-Robert_P_Sayle.html file:///Z:/VMWare_Shared/Bourne_Shell_Programming-Robert_P_Say...

5 de 102 11/10/2010 16:13

to the shell's customizability. Moreover, the branching and looping facilities of the shell allow it to makeprogrammed choices and to repeat commands as necessary. This is an invaluable quality lost in thepoint-and-click world of a windowing system.

As mentioned previously, there are a number of shells to choose from, but it must be noted that there areonly two basic types. The first is the Bourne shell. It has a unique syntax different enough from traditionalprogramming languages to require concerted study by those willing to learn it, but it is does have some veryfamiliar constructs that also keep it from being an entirely foreign language. The second type is based uponthe C shell. It is named the C shell because its syntax derives from the C programming language. It is infact similar enough syntactically to allow C programmers to quickly use the branching, looping, andassignment mechanisms. One might easily be able to write if statements and while loops, but theenvironment is different enough to cause problems if not treated carefully. A user might easily mistake astring for a variable dereference.

From these two shell types came a number of highly-interactive shells. The Korn shell, ksh, and the BourneAgain shell, bash, use Bourne shell syntax. In general, any command useable by the Bourne shell may beused by these others. The C shell's cousin is the tcsh. These derivatives are more user-friendly compared totheir parents. They usually provide hot-keys for utilities like file name completion, directory searching,in-line editing, and an extended environment. In some cases, such as the Korn shell, they also allowcomplex data structures such as arrays. These shells are definitely preferrable for daily use. It is much nicerto hit the tab key and have the shell complete a directory name than it is to have to type the whole thing,especially when the exact name is unclear. And the beautiful part is when something like a loop is neededto allow a command to be repeated, the facilities are right there. Plus, there is often a history buffer thatcaches recently issued commands so the user does not have to reenter or restructure it. But it is the basicsthat are explained herein.

This book presents the syntax and use of the Bourne shell. It does so for a very good reason; namely, it isthe most widely available of any shell. It is the first shell that came with UNIX. Consequently, it hassurvived and is distributed with every flavor of UNIX produced today. It can be found in every UNIX as/bin/sh. Because of its availability, any program written that uses it is nearly guaranteed to run on anyother UNIX. Of course portability carries only as far as the actual operating system commands allow, but atleast the basic programming tools and syntax of the Bourne shell are consistent. From this point forward,any mention of the shell refers to the Bourne shell unless explicitly stated otherwise.

Before diving right into the usual programming bag o'tricks such as variables, functions, if statements, loops,and other fun stuff, this chapter will conclude with some very important syntatical features. Without athorough understanding of them, it is extremely difficult to read and write shell scripts. For those unfamiliarwith the shell or UNIX in general, return to this chapter occassionally and reconsider them in the context ofcommands given at the prompt or within other shell scripts encountered.

1.3 Grouping Commands

Commands passed to the shell may be grouped in five ways with a semicolon (;), parentheses (()), curly braces ({}), double ampersands (&&), or double vertical bars (||). The first three may be simply viewed aspunctuation for combining multiple commands on a single line. Of course there are subtle differencesbetween them that will be discussed shortly, but in general, they are to the shell as periods are to prose. Thelast two, the double ampersand and vertical bars, are conditional conjunctives.

The semicolon allows users to string commands together on a single line as if one command was beingissued. Each command within the semicolon separated list is executed by the shell in succession. Forexample, the line below has two commands joined with a semicolon:

$ cd /var/yp; make

When entered, the shell parses the command line up to the first semicolon. The words read to that point areexecuted. In the example, the current directory is changed to the /var/yp directory. The shell then continues

Bourne_Shell_Programming-Robert_P_Sayle.html file:///Z:/VMWare_Shared/Bourne_Shell_Programming-Robert_P_Say...

6 de 102 11/10/2010 16:13

parsing the command line until it reaches the end of the line. The rest of the command is then executed, andas every good UNIX administrator knows, the NIS maps are built as a result of the make that is issued. Butthis example is not a lesson in system administration. It is a demonstration of how the shell executes eachsemicolon separated command in the order it is listed after the prompt as if the user had typed the same setof commands on separate lines.

The parentheses, when grouped around a command, cause the command to be executed in a subshell. Anyenvironmental changes occuring within the subshell do not persist beyond its execution; in other words, allchanges to the working environment of the commands issued within the parentheses do not affect theworking shell. Consider the following example:

$ cd; lsbin etc include man tmp$ cd bin; ls; lsbash gunzip gzip zcatbash gunzip gzip zcat$ cd$ (cd bin; ls); lsbash gunzip gzip zcatbin etc include man tmp

The first command ensures that the home directory is the current working directory and then lists itscontents. This is followed by changing directories into bin and then listing its contents twice with the lscommand. The next command then returns the shell to the home directory. The last command shows howparentheses behave. Two commands are issued within the scope of the working shell. The first is acompound command delimited by the parentheses. The compound command executes in a subshell. Withinthis subshell, the current directory is once again set to bin, and its contents are listed. As soon as thesubshell terminates, the parent shell resumes control. It lists the contents of the working directory, and as isdemonstrated by the resulting output, the shell's current working directory has not been modified by thesubshell's directory change.

Like parentheses, curly braces can be used to group a set of commands; however, the commands executewithin the context of the current shell. Continuing with the previous example, suppose the last command isreissued with curly braces instead of parentheses.

$ { cd bin; ls; }; lsbash gunzip gzip zcatbash gunzip gzip zcat

Notice that this time the bin directory is listed twice. Obviously, the directory change persisted. The shellexecuted the compound command instead of deferring it to a subshell as it did when parentheses were used.Note also the construction of commands with culry braces. White space precedes and follows the enclosedcommands, and a semicolon terminates the last command. The terminating semicolon is required. Certainly,the use of curly braces can be unwieldy:

$ { cd bin; ls }; ls> ^C$ { cd bin; ls }; ls }> ^C$ { cd bin; ls }; ls; }}: No such file or directorybash gunzip gzip zcat

The first command is entered the same way it would be issued if parentheses were used. When entered, theshell responds with the secondary prompt meaning that it expects more input. The job is killed with acontrol-c, and the command is reentered with a different format. Here, an extra closing brace is appended tothe command. The user assumes that the shell was expecting a terminating brace at the end of the line.Unfortunately, the results are the same. After killing the second attempt, the user tries a third time andappends a semicolon to the final lscommand. As can be seen, the command becomes syntactically correct, but the results are probably not whatthe user desires. Focusing on the semicolons reveals what happened. The shell changes directories to bin. Then it tries to list the } directory. The shell interprets the closing brace to be an argument to the ls

Bourne_Shell_Programming-Robert_P_Sayle.html file:///Z:/VMWare_Shared/Bourne_Shell_Programming-Robert_P_Say...

7 de 102 11/10/2010 16:13

command. Since no such file exists, it complains. It finishes by listing the current directory.

Hopefully, from this example, the point is clear. Curly braces are, frankly, difficult to use for groupingcommands, and when also using culry braces for accessing variable values, code becomes very difficult tofollow. It is generally good practice not to use curly braces for command grouping. Use culry braces forvariables instead.

The last two conjunctions, && and ||, combine commands conditionally. When the shell encounters either ofthese, it always executes the first half of the command. Then depending upon its results, the second half maybe run. In the case of the double ampersand, the shell handles the second part if and only if the first partexited with a status of zero. This indicates that the first half executed with no errors.

$ cd /usr/bogus && ls/usr/bogus: does not exist$ cd /var/spool && lscalendar cron locks lp mqueue pkg

Since /usr/bogusis not a valid directory, the cd command informs the user and returns with a non-zero status. The shell readsthe status and, seeing it is non-zero, does not execute a directory listing. On the other hand, /var/spool is a real directory. The cd command, in this case, performs the change and returns with a zero exit status. Again,the shell reads the status, and because it is zero, the shell proceeds with the following command, the ls.

The double vertical bars function the same except that the second half executes if and only if the first halfexits with a non-zero status.

$ ls -ltotal 4drwxr-xr-x 2 rsayle fvsw 512 Jul 15 14:51 bindrwxr-xr-x 2 rsayle fvsw 512 Jul 15 14:51 man-rw-r--r-- 1 rsayle fvsw 0 Jul 15 14:51 profile$ (ls -l | grep "root") || echo "No files owned by root"No files owned by root

The first listing above shows that all files in the current directory are owned by rsayle. Hence, the grep for root fails. A non-zero exit code is returned by the subshell that executed the ls and the grep. The parent shell, having encountered ||, acknowledges the result and prints the informational message accordingly.

1.4 I/O

The UNIX Bourne shell contains a couple methods for manipulating the input and output of commands. Thefirst method employs pipes. It can be used to chain commands together so that each preceding commandsends its output to the input of the following command. The second method is known as redirection, and itprovides the ability to pull input from or place output into other sources besides the shell window. Butbefore introducing these commands, a general understanding of the standard I/O facilities must be reached.

The input and output displayed on the console comes from three buffers: stdin, stdout, and stderr(pronounced "standard in," "standard out," and "standard error," respectively). These devices arecollectively referred to as the standard I/O. Stdin is the terminal's default input. The shell reads commandsand answers to prompts from stdin. The default output is stdout. The console prints anything it receives fromstdout. In general, this includes print statements from the programs run at the command line and warningmessages broadcast by system administrators. Like stdout, the shell also displays the error messagesreceived from stderr on the console, but it is imperative to keep in mind that stderr is not the same as stdout.A good way to remember this is to think of stdin, stdout, and stderr as three different files. In fact, this isexactly what they are. Stdin is file descriptor zero; stdout, one; and stderr, two. As such, each may bereferenced by its file descriptor number. Section 8.1, Advanced Shell Topics: More I/O, shows how to manipulate the standard I/O using the file descriptor numbers.

A pipe, represented by a single vertical bar (|), connects the output of its left operand to the input of its right

Bourne_Shell_Programming-Robert_P_Sayle.html file:///Z:/VMWare_Shared/Bourne_Shell_Programming-Robert_P_Say...

8 de 102 11/10/2010 16:13

operand. The operands of pipes may be any legal shell command. The goal of piping is to pass outputthrough a series of commands that will massage the data into a more meaningful representation. Normally,the shell parses stdin and passes the input to the command given. The command operates on the input, andin turn, presents the output to stdout. The shell handles pipes as is shown in the figure below. Here, the shellbegins as it always does. It parses stdin passing arguments to the command issued. The program runs andprovides output. Seeing a pipe, the shell takes the resulting output and passes it as the input to the nextcommand. The next command works on the new input and creates a new ouput which is passed on to thefollowing command in the pipe chain. This continues until the shell reaches the final command. The lastcommand receives the massaged data from the previous command, runs, and then places the result intostdout. The shell the displays stdout to the user.

Figure 1.4-1. A Graphical Depiction of How Pipes Combine Multiple Commands

The most common use of pipes is for filtering. Typically when filtering, a program prints text to stdoutwhich is piped into a utility such as grep, sed, or awk. The utility selects data from the input according to apattern specified as an argument. The result is usually posted to the screen for visual inspection, but it ispossible to save the results in a file, store it as the value of a variable, or repipe it into another command.

$ ypcat passwd | grep "^[a-zA-Z0-9]*::" | cut -f1 -d:jbeadlebuild35cwoodrumdborwinjbelldfellbaum

The example above shows how grepand pipes can be used as a filter. The command is actually three programs separated by pipes. The firstcommand, ypcat passwd, displays the network information services (NIS) map for the password database.The shell, as directed by the pipe, sends the map to grep. Grep takes the map and searches for all accountsthat do not have a password; this is the filtering operation. Since the goal is to list the accounts, some furtherprocessing must be completed so the output is passed to cut. Cut collects the data and trims everything fromeach line except the account name. The final output is displayed as shown. The figure below depicts theexample graphically.

Figure 1.4-2. A Graphical Depiction of a Pipe Chain

Whereas piping permits the chaining of commands such that the output of one serves as the input of another,redirection can be used to take the input for a program from a file or to place the output of a program into afile. Not surprisingly, these two techniques are known as input redirection and output redirection. Inputredirection takes the form: command [input redirect symbol] input. It is equivalent to saying, "Execute acommand using the data that follows." Redirecting the output says, "Execute a command and place theprinted results into this file," and it has the form: command [output redirect symbol] destination.

$ cat < /etc/defaultdomainfvo.arinc.com$ ypcat hosts > hosts.bak$ cat hosts.bak127.0.0.1 localhost144.243.92.13 blatz

The previous commands demonstrate input and output redirection. The first cat command takes as its argument the file /etc/defaultdomain. The argument is given to cat via input redirection, the left brace.

Bourne_Shell_Programming-Robert_P_Sayle.html file:///Z:/VMWare_Shared/Bourne_Shell_Programming-Robert_P_Say...

9 de 102 11/10/2010 16:13

Output redirection is shown with ypcat. The ypcat command, when applied to the NIS hosts map, lists allthe IP addresses and names of each node in the NIS domain. Here, the output is redirected into the filehosts.bak.

Two types of input redirection exist. The first one uses one left brace. It redirects standard input to be takenfrom the file specified. For example, if an administrator wanted to know how many accounts were on aparticular machine, input redirection of the /etc/passwd file into wc could be used.

$ wc -l < /etc/passwd 12

The second type employs a double left brace. Instead of taking the input from a file, the input is taken at thecommand line between two delimiters.

$ wall << WARNING> "itchy will reboot at 6PM this evening"> WARNINGBroadcast Message from rsayle (pts/0) on itchy Wed Jul 24 14:06:57..."itchy will reboot at 6PM this evening"

The delimiters may be any word. In this case the word "WARNING" is used. The user here issues the warn allusers command, wall. The user also instructs the shell to use as the text of the message the lines that fallbetween the two WARNING delimiters.

Similarly, there are two types of output redirection. The symbols used are simply inverses of the inputredirectors; right braces are used instead of left braces. And of course, the semantics are nearly opposite. Inthe case of the single right braces, stdout is redireced to a file. The file is created if it does not exist or it isoverwritten if the file named already exists and globbing has been enabled (set glob). Below, the shell copies the file combo to the file combo.bak using output redirection. The output from the second catcommand is redirected into the new file combo.bak.

$ cat combo10 23 5$ cat combo > combo.bak$ cat combo.bak10 23 5

Double right braces have the same effect as single braces except if the file already exists, the output isappended to the file instead of overwriting it.

$ cat combo >> combo.bak$ cat combo.bak10 23 510 23 5

Continuing with the previous example, if the user instructs the shell to once again display the contents of thefile combo but uses double right braces, then combo.bak ends up with two lines, each being the single linefrom combo.

As a final I/O basics topic, it is helpful to mention that there are a few generic devices available forredirection besides the standard I/O. Some flavors of UNIX provide devices for the terminal, /dev/tty, theconsole, /dev/console, and the great bit bucket in the sky, /dev/null. All output sent to /dev/null is simply discarded. It is useful in shell scripts when output should be hidden from the user but should not bestored. There is a subtle difference between the console and the terminal. In a windowed environment, a ttyterminal is associated with each shell session; in other words, each command line window such as an xtermis a separate terminal. Redirection to /dev/ttysends output to the active window. The console, on the other hand, is the screen. It is the monitor in general.Most windowing programs provide a special switch to the shell windows that will link /dev/console to thewindow instead of writing output directly on the screen. In any event, output can be redirected to thesedevices quite easily.

$ cat /etc/motd >>/dev/ttySun Microsystems Inc. SunOS 5.4 Generic July 1994

Bourne_Shell_Programming-Robert_P_Sayle.html file:///Z:/VMWare_Shared/Bourne_Shell_Programming-Robert_P_Say...

10 de 102 11/10/2010 16:13

$ cat /etc/motd >>/dev/null$

1.5 Regular Expressions

Regular expressions are one of the keys to UNIX. To master them is to open the door to a new universe ofcommands. A user who has a firm grasp of regular expressions can perform multiple tasks with onecommand, filter text for processing, and edit multiple files nearly instantaneously. And these are but a fewexamples of the power regular expressions provide.

A regular expression is a sequence of characters specifying a textual pattern. They instruct programs toprocess only those items that match the pattern. Most regular expressions use metacharacters to expressrepetition, existence, or ranges of character patterns. It can be very difficult at first to decipher a regularexpression that uses metacharacters. Nevertheless, it is extremely important to practice creating and readingregular expressions. It is truly the difference between writing robust scripts and simply browsing aroundthrough UNIX.

^$[a-z][0-1][a-z0-1]*^<tab>[A-Z][a-zA-Z0-9 ]*$10\{3,6\}

At first glance, to the novice user, the examples above look like gibberish, but a patient and disciplined usercan understand what each pattern means. In the first line, the carat represents the beginning of a line and thedollar sign the end of a line. Together, with nothing in between, they form a pattern describing an emptyline. The second example shows how character classes can be used in regular expressions. The patternrepresents a line that contains a word beginning with a lower case letter, followed by a zero or one andending with any number of lower case letters, zero, or one. The square braces denote character classes, andthe asterisk is a wildcard meaning any number including zero of the previous pattern. The third example iseven more restrictive than the second. It matches patterns beginning with a tab (the tab normally is justtyped; the <tab>is shown here for illustrative purposes), followed by an upper case letter, followed by any number of lowercase, upper case, digit, or space character, and terminated by the end of the line. It is certainly a complexexample, but it shows how by examining the pattern piece by piece, the meaning becomes clear. The lastexpression matches the number range 103 through 106, inclusive. It specifies a pattern beginning with tenfollowed by any number in the range three through six.

A list of the metacharacters and their usage is given in the table below. The five metacharacters at the endare part of an extended set. They only have meaning in certain programs.

Bourne_Shell_Programming-Robert_P_Sayle.html file:///Z:/VMWare_Shared/Bourne_Shell_Programming-Robert_P_Say...

11 de 102 11/10/2010 16:13

Table 1.5-1. Metacharacters Metacharacter Usage Used by

. Matches any single character except newline. Allutilities

*Matches zero or more occurrences of the single character that immediately precedes it. The character may be specified by a regular expression.

Allutilities

[]

Matches any one of the class of characters enclosed between the brackets. I fa caret (^) is the first character inside the brackets, then the match is reversed.A hyphen is used to specify a range. In order to match a cl ose bracket, it mustbe the first character in the list. Other metacharacters l ose their specialmeaning when enclosed within brackets so that they may be matc hedliterally.

Allutilities

^As the first character of a regular expression, matches the beginning of a line.

Allutilities

$ As the last character of a regular expression, matches the end of a line. Allutilities

\ Escapes the metacharater which immediately follows. Allutilities

/{m,n/}

Matches a range of occurrences of the single character that immediately precedes the expression. If only m is given, then the pattern matches exactly mrepetitions. If m, is specified then at lea st m occurrences are needed for a match. The use of m,n matches any number of repetitions between mand n.

sed, grep, egrep,awk

+ Matches one or more of the preceding regular expression. egrep,awk

? Matches zero or one instance of the preceding regular expression. egrep,awk

| Matches either the preceding or following regular expression. egrep,awk

() Groups regular expressions. egrep,awk

Bourne_Shell_Programming-Robert_P_Sayle.html file:///Z:/VMWare_Shared/Bourne_Shell_Programming-Robert_P_Say...

12 de 102 11/10/2010 16:13

There are a number of utilities available that use regular expressions. In particular, it is useful to study theone built into the Bourne shell, filename completion. Filename completion assists in locating files or lists offiles. It uses its own set of metacharacters: *, matches zero or more chracters; ?, matches any single character; [], matches any single character listed (a ! reverses the match); and \, removes the meaning ofany special character including and especially the end of a line. * is typically called the wildcard, and \ isreferred to as escape. Filename completion works by causing the shell to expand the pattern into all filenames that match the expression.

$ ls as*.*ascendmax.alert ascendmax.emerg ascendmax.info ascendmax.warningascendmax.crit ascendmax.err ascendmax.notice$ cd ..$ ls log/as*.*log/ascendmax.alert log/ascendmax.err log/ascendmax.warninglog/ascendmax.crit log/ascendmax.infolog/ascendmax.emerg log/ascendmax.notice

As part of its parsing routine, the shell expands regular expressions before executing the command listed.The expansion acts as if the user had typed a list of files at the command line. In the preceding example, theshell expands the pattern as*.* into all files in the current directory beginning with as and having a period somewhere in the middle. The result is substitued for the pattern and the command is issued to list the files.The second part shows how the shell can be directed to traverse directory trees and perform expansion onsubdirectories.

Some of the other common programs that use regular expressions are given in the table below.

Table 1.5-2. Programs that Use Regular Expressions

Utility Purpose Reference for FurtherReading

grep Prints each line from its input that matches the pattern specified. man

sedPermits line by line editing of its input according to the script of regularexpressions and commands given.

man, Sed & Awk(O'Reilly & Associates)

awkProgramming language for extracting and formatting input according totextua l patterns. Works best when the input already has a particularstructure.

man, Sed & Awk(O'Reilly & Associates)

cutExtracts text from the input given a list of field numbers and their field separator. man

uniqFilters out repeated lines from its input so that only unique lines remain in the output. man

sort Sorts the lines of input given. man The most common forms of input to these utilities are files or pipes:

$ grep "defs.h" *.cpp | cut -f1 -d: | uniqbench.cppbitvec.cppbstream.cppgimp.cppidendict.cppmatch.cppregexp.cpprwbag.cpprwbagit.cpprwfile.cpprwtimeio.cppstrngcv.cpptoolmisc.cpputility.cppwcsutil.cppwstring.cpp

This command uses files and pipes. The user wants to list all C++ source files that include the header file

Bourne_Shell_Programming-Robert_P_Sayle.html file:///Z:/VMWare_Shared/Bourne_Shell_Programming-Robert_P_Say...

13 de 102 11/10/2010 16:13

defs.h. The command begins by searching for the string defs.h in all files ending with the extension .cpp. The files found are passed to cut. Normally, grep prints the file name and every line in a file that containsthe pattern. Here, cut is used to trim the lines down to the file name only. The result is in turn passed touniq which filters out duplicated lines.

1.6 Quoting

Just as regular expressions separate UNIX wizards from users, the use of quoting differentiates shellscripters from shell hackers. Quoting is simple in form and powerful in semantics. With quoting users canmake metacharacters act like regular text or nest commands within others. A long command can be easilyseparated onto multiple lines by using quotes. But with this powerful tool comes a price. Proper usage ofquoting is at best subtle. Often it is the source of program error and is difficult to debug. This should notdeter scripters from quotes. Instead, users must simply realize that quoting takes practice.

Bourne shell quotes come in four forms:

1. Double Quotes ""Removes the special meaning of all enclosed characters except for $, `, and \.

2. Single Quotes ''Removes the special meaning of all enclosed characters.

3. Back Quotes ``Command substitution; instructs the shell to execute the command given between the quotes andsubstitute the resultant output.

4. Escape \Removes the special meaning of the character that follows. Inside double quotes, \ also escapes $, ', newline, and escape itself.

The primary use of double and single quotes is to escape the meaning of metacharacters within blocks oftext:

$ echo *bin man profile$ echo "*"*$ echo $USERrsayle$ echo "$USER"rsayle$ echo '$USER'$USER

The first two commands show the difference between using quotes. Special characters are interpreted by theshell when they are not quoted as in the case where the echo command displays the contents of the currentdirectory: bin, man, and profile. The same can be said of the third command in which the value of theenvironment variable USER is echoed. Moreover, it is important to remember that there are a few specialcharacters that are still interpreted when used with double quotes. Single quotes must be used to perform theescape as is shown by the last few commands.

Double quotes and single quotes are especially useful for patterns containing white space. Suppose, forexample, an administrator knows a person's name but can't remember the user's account name. Filtering thepasswd database through grep might be a good way to figure out the account name.

Bourne_Shell_Programming-Robert_P_Sayle.html file:///Z:/VMWare_Shared/Bourne_Shell_Programming-Robert_P_Say...

14 de 102 11/10/2010 16:13

$ ypcat passwd | grep Robert Saylegrep: can't open Sayle

Grep balked on the user's name! Well, not exactly. Due to its syntax, grep tried to find the string "Robert" in the file "Sayle." The trick is to instruct the shell to ignore the white space between the first and last name.

$ ypcat passwd | grep "Robert Sayle"rsayle:hOWajBzikAWRo:328:208:Robert Sayle:/home/rsayle:/bin/tcsh

The double quotes tell the shell interpreter to combine all the text contained into one argument, "Robert Sayle." Grep receives the argument and uses it as the key during its search through the output of the ypcat.

To search for quotes within text, single quotes escape double quotes and vice-versa. The following examplesillustrate this. The first and third show how one cannot simply use a quotation mark in pattern matching. Theshell assumes that more text follows the input. The other examples show how to do it correctly.

$ grep " sloc> ^C$ grep '"' sloc# a given set of files; finds each ";" which denotes an echo "sloc: usage: sloc file1 [file2 ...]" LINES=`grep ";" ${1} | wc -l`echo "${RESULT} lines of code"$ grep ' tgzit> ^C$ grep "'" tgzit# tgzit -- tar's and gzip's the filenames passed

Although these are simple examples, not enough can be said about the difference between single and doublequotes. Double quotes function perfectly in most cases, but there are times when a programmer should useone over another. The realm of possibilities will be explored in succeeding subsections as new topics arepresented. For now, a good rule of thumb is to use double quotes in order to group words into a singleargument, to allow variable substitution as in "$USER", or to allow command substitution. Single quotesshould be used when no substitution should occur. With that said, the discussion of quotes in generalcontinues.

Back quotes are arguably the most powerful form of quoting since they permit a level of nesting calledcommand substitution. In other words, they allow commands to be executed within other commands. Whenthe shell scans a command line and finds an embedded command between back quotes, it runs the nestedcommand. The output of the nested command is then substituted as part of the enclosing command which issubsequently executed. As an example, suppose a programming team places their source files in a commonrepository.

$ ls -l *.h-r--r--r-- 1 rsayle fvsw 5373 Aug 3 1995 adminlink.h-r--r--r-- 1 rsayle fvsw 5623 Aug 7 1995 agentadmin.h-r--r--r-- 1 rsayle fvsw 4930 Aug 10 1995 agentadminexception.h-rw-r--r-- 1 lshar fvsw 20264 Aug 14 1995 attribute.h-rw-r--r-- 1 lshar fvsw 3346 Aug 14 1995 attributeexception.h-rw-r--r-- 1 lshar fvsw 6819 Aug 14 1995 attributehelper.h-rw-r--r-- 1 lshar fvsw 3424 Aug 14 1995 attributehelperexception.h-rw-r--r-- 1 lshar fvsw 7446 Aug 14 1995 attributereference.h-rw-r--r-- 1 lshar fvsw 3394 Aug 14 1995 attributerefexception.h-rw-r--r-- 1 rsayle fvsw 35012 Aug 16 1995 attributevalue.h-r--r--r-- 1 rsayle fvsw 4959 Jul 25 1995 avdictionary.h-rw-r--r-- 1 lshar fvsw 4851 Aug 17 1995 avexception.h-rw-r--r-- 1 rsayle fvsw 5024 Jul 25 1995 avtype.h-rw-r--r-- 1 lchang fvsw 9106 Jul 19 1995 computeropstatemsg.h-rw-r--r-- 1 346 fvsw 8627 Jul 25 1995 elevation.h-rw-r--r-- 1 346 fvsw 9454 Jul 25 1995 latitude.h-rw-r--r-- 1 rsayle fvsw 5025 Aug 3 1995 linkagentadmin.h-rw-r--r-- 1 lshar fvsw 6260 Jun 19 1995 linkfactory.h-rw-r--r-- 1 rsayle fvsw 4871 Jul 26 1995 linkmibloader.h-rw-r--r-- 1 346 fvsw 9512 Jul 25 1995 longitude.h-rw-r--r-- 1 lshar fvsw 17087 Aug 14 1995 managedobject.h-rw-r--r-- 1 lshar fvsw 14056 Aug 14 1995 mib.h-rw-r--r-- 1 lshar fvsw 3268 Aug 14 1995 mibexception.h-r--r--r-- 1 rsayle fvsw 5263 Aug 2 1995 mibloader.h

Bourne_Shell_Programming-Robert_P_Sayle.html file:///Z:/VMWare_Shared/Bourne_Shell_Programming-Robert_P_Say...

15 de 102 11/10/2010 16:13

-r--r--r-- 1 rsayle fvsw 4910 Aug 10 1995 mibloaderexception.h-rw-r--r-- 1 lshar fvsw 3255 Aug 14 1995 moexception.h-rw-r--r-- 1 lshar fvsw 8101 Aug 23 1995 mofactory.h-rw-r--r-- 1 lshar fvsw 3346 Aug 14 1995 mofactoryexception.h-rw-r--r-- 1 lshar fvsw 6134 Aug 14 1995 mofactoryhelper.h-rw-r--r-- 1 lshar fvsw 3424 Aug 14 1995 mofactoryhelperexception.h-rw-r--r-- 1 lchang fvsw 5008 Aug 7 1995 msgsvccbhandler.h-rw-r--r-- 1 lchang fvsw 3232 Aug 7 1995 msgsvccbhdlrexcep.h-rw-r--r-- 1 lchang fvsw 7365 Aug 7 1995 msgsvchandler.h-rw-r--r-- 1 lchang fvsw 3215 Aug 7 1995 msgsvchdlrexcep.h-rw-r--r-- 1 rsayle fvsw 4823 Aug 10 1995 newavexception.h-r--r--r-- 1 rsayle fvsw 3815 Aug 10 1995 nmmsgids.h-rw-r--r-- 1 346 fvsw 9342 Jul 25 1995 operationalstate.h-rw-r--r-- 1 rsayle fvsw 10085 Aug 7 1995 opstatemsg.h-r--r--r-- 1 rsayle fvsw 4790 Aug 10 1995 ovsexception.h-rw-r--r-- 1 lshar fvsw 4174 Aug 14 1995 parserhelper.h-rw-rw-rw- 1 lchang fvsw 3207 Aug 17 1995 regidexception.h-rw-rw-rw- 1 lchang fvsw 9503 Aug 17 1995 regidserver.h-rw-r--r-- 1 346 fvsw 10514 Jul 25 1995 rollablecounter.h-r--r--r-- 1 tbass fvsw 2423 Jul 12 1995 servicedefs.h-r--r--r-- 1 rsayle fvsw 2785 Jul 26 1995 startup.h-r--r--r-- 1 rsayle fvsw 5210 Aug 7 1995 svagentadmin.h-rw-r--r-- 1 lshar fvsw 7909 Aug 17 1995 svfactory.h-r--r--r-- 1 rsayle fvsw 5544 Aug 2 1995 svmibloader.h-r--r--r-- 1 rsayle fvsw 10938 Aug 24 1995 svovshandler.h

The ls command output can be simply piped to grep with the account name, but in order to generalize thescript, command substitution could be used.

$ whoami rsayle$ ls -l *.h | grep `whoami`-r--r--r-- 1 rsayle fvsw 5373 Aug 3 1995 adminlink.h-r--r--r-- 1 rsayle fvsw 5623 Aug 7 1995 agentadmin.h-r--r--r-- 1 rsayle fvsw 4930 Aug 10 1995 agentadminexception.h-rw-r--r-- 1 rsayle fvsw 35012 Aug 16 1995 attributevalue.h-r--r--r-- 1 rsayle fvsw 4959 Jul 25 1995 avdictionary.h-rw-r--r-- 1 rsayle fvsw 5024 Jul 25 1995 avtype.h-rw-r--r-- 1 rsayle fvsw 5025 Aug 3 1995 linkagentadmin.h-rw-r--r-- 1 rsayle fvsw 4871 Jul 26 1995 linkmibloader.h-r--r--r-- 1 rsayle fvsw 5263 Aug 2 1995 mibloader.h-r--r--r-- 1 rsayle fvsw 4910 Aug 10 1995 mibloaderexception.h-rw-r--r-- 1 rsayle fvsw 4823 Aug 10 1995 newavexception.h-r--r--r-- 1 rsayle fvsw 3815 Aug 10 1995 nmmsgids.h-rw-r--r-- 1 rsayle fvsw 10085 Aug 7 1995 opstatemsg.h-r--r--r-- 1 rsayle fvsw 4790 Aug 10 1995 ovsexception.h-r--r--r-- 1 rsayle fvsw 2785 Jul 26 1995 startup.h-r--r--r-- 1 rsayle fvsw 5210 Aug 7 1995 svagentadmin.h-r--r--r-- 1 rsayle fvsw 5544 Aug 2 1995 svmibloader.h-r--r--r-- 1 rsayle fvsw 10938 Aug 24 1995 svovshandler.h

The whoami command returns the current account name. When used in the script, the shell executes whoamiand substitutes the result for the argument to grep. Hence, anyone using the script would get results specificto their account name.

Finally, the last quoting mechanism is the escape represented by a backslash character, \. It removes the special meaning of the character immediately following. The most common use is for line continuation:

$ /usr/bin/dump 0usbdf 6000 126 54000 /dev/nrst0 \> /dev/sd0h >> backup.log 2>&1

The long backup command above could not fit within one line so an escape was entered before the end ofthe line. It escaped the newline causing the shell to wait for more input before parsing the command.

Actually, a backslash escapes the special meaning of any character immediately following it. Returning tothe first example in which single and double quotes were used to escape metacharacters, a back slash canprovide some similar and some not so familiar results.

$ echo *; echo "*"; echo \*bin man profile*

Bourne_Shell_Programming-Robert_P_Sayle.html file:///Z:/VMWare_Shared/Bourne_Shell_Programming-Robert_P_Say...

16 de 102 11/10/2010 16:13

*$ echo $USER; echo "$USER"; echo '$USER'; echo \$USER; echo \$$USERrsaylersayle$USER$USER$rsayle$ echo hello; echo "hello"; echo hell\ohellohellohello

As can be seen, the escape functions most like single quotes, but it is imperative to keep in mind that it doesso for exactly one character following the back slash. The last two echo permutations of the USER variabledemonstrate this fact. As an aside, the last command string shows how escapes can precede characters thathave no hidden meaning to the shell. There is no effect.

For the final example, double quotes and escapes are used to set a variable's value for which the directorylist is too long to fit upon one line:

$ DIRS2ARCHIVE=" \> $HOME/tmp/bin \> $HOME/tmp/man \> $HOME/tmp/profile \> "$ echo $DIRS2ARCHIVE/home/rsayle/tmp/bin /home/rsayle/tmp/man /home/rsayle/tmp/profile

2. Introduction to ScriptingCustomizing UNIX with Shell Scripts1.A Simple Example2.

2.1 Customizing UNIX with Shell Scripts

Why the heck would anyone want to write shell scripts? After all, a script is just a bunch of commands thatcould be issued from the shell prompt anyway. Well, sure. But how many times does a user have to reenterthe same command before his fingers become knotted and arthritic? Wouldn't it be easier to store thosecommands in a file that can be executed by the computer? Certainly the CPU can execute commands fasterthan a human can type. Besides, isn't it smarter to modularize tasks so they can be combined and reused inan efficient manner? The fact of the matter is that shell scripts can provide this and more.

Shell scripts can help organize repeated commands. An administrator who often repeats a series ofoperations can record the command set into a file. The administrator can then instruct the shell to read thefile and execute the commands within it. Thus, the task has been automated; one command now does thework of tens, hundreds, even thousands of others.

Shell scripting helps users handle the operating system smarter. The techniques presented in this discussioncan be applied at the command line prompt. They are, after all, commands themselves; albeit, they are builtinto the Bourne shell. Still, by developing a habit of scripting at the command line, users can save time andeffort by applying a script to a task rather than executing it by brute force. A good example is processing aset of files with the same command. A user can type the same command with a different file name over andover, or the user may write a loop that iterates over the files and automatically generates the same commandset.

Bourne_Shell_Programming-Robert_P_Sayle.html file:///Z:/VMWare_Shared/Bourne_Shell_Programming-Robert_P_Say...

17 de 102 11/10/2010 16:13

Furthermore, shell scripting is simpler than conventional programming. Unlike compiled languages such asC and C++, scripts are interpreted. There is no compiling statements into machine code and linking it withlibraries to form an executable. The script is the executable. Commands entered into it are immediatelyready to run. On the other hand, scripts run slower than compiled programs and are often harder to debugmainly because no debugging tools are available. Even so, shell scripts are easier to develop and modify.

Hopefully, the point is clear. Scripting is powerful, simple, and fast. Anything that someone can think ofentering at the command line can be automated by a shell script:

Reformat a file set so that the only white space in every file are spaces.Translate a file into another format for printing. Startup and shutdown a system. Extract and insert information from and into a database. Clean up a UNIX account. Collect and present system performance measurements. Monitor a remote workstation or accounts. Prepend a template prologue to source code for documentation. And all the other thinks you can think!

2.2 A Simple Example

At the risk of sounding cliche, a good shell script reads like a well written story. All right, maybe not a story, but it does take a form similar to a classical essay. It has an introduction, a body, and a conclusion.The introduction prepares the script much like the reader of an essay is prepared for the paper's contents byits introduction. A script's body presents commands to the shell just as an author presents an argumentthrough an essay's body. And, in both cases, the conclusion wraps up loose ends. The shell script listedbelow illustrates this point. It is read from top to bottom with each line encountered being executed insequence. Normally, the text is stored within a file that has execute permissions set. A user simply enters thefile's name at the command line prompt, and the program is run. For illustrative purposes, the script isshown.

#!/bin/sh# cleandir -- remove all the large, unused files from the current directory

echo "Removing all object files..."rm *.o # all object files have a .o extensionecho "done."echo "Removing core dumps..."rm core # there's only ever one coreecho "done."exit 0

Before analyzing the script, a quick note about comments is deserved. Comments are text that is ignored bythe interpreter. Programmers provide comments to describe their code to readers. Comments begin with ahash mark (#). They may start anywhere within a line, and the comment always extends from the hash markto the end of the line. A comment spans one line only; multiple line comments are not allowed. The scriptabove shows the two types of commenting. The file prologue comment extends across a whole line:

# cleandir -- remove all the large, unused files from the current directory

On the other hand, the comments following the remove statements are inlined. They share the same line withthe command:

rm *.o # all object files have a .o extension...rm core # there's only ever one core

The first line of the script is not a comment. Instead, it specifies which shell shall be used to process thesucceeding commands. Considering the comparisson of a script to an essay, it can be said that the first line

Bourne_Shell_Programming-Robert_P_Sayle.html file:///Z:/VMWare_Shared/Bourne_Shell_Programming-Robert_P_Say...

18 de 102 11/10/2010 16:13

introduces the script's shell. It is the introduction. The basic syntax for declaring the shell has the form:#![full path to shell program]. The exclamation point following the hash mark signals the interpreterthat the line is not a comment. It tells the interpreter that the text that follows is the path to a program inwhich to execute the script. In the example above, the Bourne shell is used, /bin/sh.

It is not mandatory for a script to specify what shell program to use. If no such specification is given, thenby default, the script is executed in a subshell of the calling shell. In other words, the script uses the sameshell as the invoking shell. This is potentially dangerous. Suppose a user operates from a different shell thanthe Bourne shell. The tcshis a good example. It has a much more user-friendly command line interface than sh, and it uses the same syntax as the csh. This syntax looks like C source code. It is very different from sh syntax. Suppose alsothat the user writes a script with Bourne shell syntax but neglects to make #!/bin/sh the first line. When the user invokes the script from the tcshprompt, the interpreter reads the script and determines what shell to use. Since no shell is specified, itinvokes a subshell of the tcsh, and executes the script. Chances are the program fails miserably. Section 9, Debugging Hints contains an example that demonstrates such a failure.

A shell script's body is the list of commands following the shell declaration. It tells the program's story: whatis it doing and how is it doing it. The shell processes the command body sequentially with branching,looping, and function constructs being exceptions to the rule. Any command that can be issued from a shellprompt may be included in a script's body. The example's body is the set of echo and rm statements:

echo "Removing all object files..."rm *.o # all object files have a .o extensionecho "done."echo "Removing core dumps..."rm core # there's only ever one coreecho "done."

This script tells the story of cleaning up a directory. It begins by informing the invoker with the echo that it will remove the object files. The next line actually performs this action with the rm on all files containing a .o extenstion in their name. This command in turn is followed by two more echo commands. The firstindicates that the previous action completed. The second informs the user what it will do next. A secondremove follows the two echostatements. It deletes any local core dumps. After completing, control passes onto the last echo which informs that user that the core dump removal finished.

By default, a script concludes as soon as it has reached the last statement in its body. At this point, it returns control to its invoker. It also returns an exit status code. The exits code of a script is the return status of thelast command run by the script. It is possible, however, to control this value by using the exit command. Ittakes the form exit status where statuscan be any non-negative integer. Zero usually indicates no errors were encountered by the script. Non-zerovalues indicate detection of various faults defined by the script. It is up to the programmer to determine whatthese faults are and to assign exit codes to them. The example terminates gracefully hen the body has beenexecuted. It signals success by returning a zero status code:

...echo "done."exit 0

It must also be noted that unlike an essay, a script can provide a conclusion anywhere within its body. To terminate the execution of a script prior to reaching its end, the exit command must be used. This isgenerally done when a program detects an unrecoverable error such as insufficient parameterization. Onceagain, scripters are responsible for determining errant conditions and deciding whether to terminate theprogram or not.

Bourne_Shell_Programming-Robert_P_Sayle.html file:///Z:/VMWare_Shared/Bourne_Shell_Programming-Robert_P_Say...

19 de 102 11/10/2010 16:13

3. VariablesShell Variables Defined1.Declaring Variables2.Properties of Variables3.Environment Variables4.Special Parameters5.Reading Data into Variables6.

3.1 Shell Variables Defined

Just like any other programming language, the shell supports variables. A variable is a logical name for astored value. The value may be changed during program execution, hence, the term variable. In the Bourneshell, a variable's name may be any string of alphanumeric characters and underscores (_). Whitespace is notallowed. Examples of legal variable names include:

COUNTHOME_DIRECTORY_file1

Illegal variable names include:

FILE*The Answerarray(i)

Some variables are predefined by the shell. They are known as environment variables and specialparameters. All other variables are user defined. It is the scripter's responsibility to declare such variablesand assign them values.

3.2 Declaring Variables

A script assigns a value to a variable by declaring the variable's name immediately followed by an equalsign (=) and the value.

COUNT=1HOME_DIRECTORY=/export/home/bugsy_file1=table.dat

No white space is permitted between the variable, the equal sign, and the value. If spaces exist in a variableassignment, the shell tries to execute either the variable name of the value as can be seen:

$ COUNT =1COUNT: not found$ COUNT= 11: execute permission denied$ COUNT=1$

To retrieve the value stored in a variable, the variable must be preceded by a dollar sign ($). Getting thevalue is called dereferencing.

$ COUNT=1$ echo $COUNT1

Just as the name of a variable cannot contain white space, neither may the value.

$ NAME=John Doe

Bourne_Shell_Programming-Robert_P_Sayle.html file:///Z:/VMWare_Shared/Bourne_Shell_Programming-Robert_P_Say...

20 de 102 11/10/2010 16:13

Doe: not found

But with the help of the shell's quoting mechanisms, it is possible to create strings that do contain whitespace and then assign them to a variable's value.

$ NAME="John Doe"$ echo $NAMEJohn Doe$ NAME='Jane Doe'$ echo $NAMEJane Doe

Moreover, the same quoting techinques can be used to place quotes in a variable's value.

$ ERROR_MSG="You can't do that!"$ echo $ERROR_MSGYou can't do that!$ A_QUOTE='"To be, or not to be"'$ echo $A_QUOTE"To be, or not to be"

Using quotes, it is even possible to store the output of a command into a variable.

$ XTERMINALS=`ypcat hosts | grep ncd[0-9][0-9] | \> cut -f2 | cut -f2 -d" " | uniq`$ echo "The NCD xterminals are $XTERMINALS"The NCD xterminals are ncd05ncd02ncd03ncd01ncd07ncd04ncd06

Filename expansion can be used to assign values to variables. The usual rules for metacharacters apply.

$ XPROGS=/usr/bin/x*$ echo $XPROGS/usr/bin/xargs /usr/bin/xgettext /usr/bin/xstr$ IPROGS=/usr/bin/i[0-9]*$ echo $IPROGS/usr/bin/i286 /usr/bin/i386 /usr/bin/i486 /usr/bin/i860 /usr/bin/i86pc

Braces can be used during variable dereferencing. It is highly recommended that programmers consistentlydelimit variables with braces. The technique has a few advantages. First, it makes variables standout withina script. Readers can easily see the variable being dereferenced within a block of code. Second, it has theadvantage of permitting the generation of expanded strings from variable values. If a script declared avariable named FILE, it could then make a backup copy of the actual value stored in FILE:

$ FILE=oracle.log$ cp ${FILE} ${FILE}.bak$ lsoracle.log oracle.log.bak

This technique would work without the braces, but notice the difference between using the braces and notusing them.

$ cp $FILE $FILE.bak

In this case, is it obvious to the reader that the second dereference is the value of FILE with .bak appended to it, or might the reader think that the variable being dereferenced is FILE.bak? By using braces to delimitthe variable, the problem is solved. Certainly it is clear that the variable is FILE not FILE.bak. Moreover,there are times when appending text to the variable name may produce unexpected results.

$ cp $FILE $FILE2cp: Insufficient arguments (1)Usage: cp [-i] [-p] f1 f2 cp [-i] [-p] f1 ... fn d1 cp [-i] [-p] [-r] d1 d2

Bourne_Shell_Programming-Robert_P_Sayle.html file:///Z:/VMWare_Shared/Bourne_Shell_Programming-Robert_P_Say...

21 de 102 11/10/2010 16:13

Here, the user attempts to create a copy of oracle.log into oracle.log2, but since digits can be part of avariable name, the shell tries to dereference the undefined variable FILE2. Third, braces allow parametersubstitution.

Parameter substitution is a method of providing a default value for a variable in the event that it is currentlynull. The construct uses a combination of braces delimiting a variable and its default. The variable anddefault value are separated by a keyword. The keyword serves as a condition for when to assign the value tothe variable. The list of keywords is shown in the following table.

Table 3.2-1. Summary of Parameter Substitution Construct Meaning $parameter

${parameter} Substitue the value of parameter.

${parameter:-value} Substitue the value of parameter if it is not null; otherwise, use value.

${parameter:=value}Substitue the value of parameter if it is not null; otherwise, use value and also assign value to parameter.

${parameter:?value}Substitue the value of parameter if it is not null; otherwise, write value to standard error and exit. If value is omitted, then write "parameter: parameter null or not set" instead.

${parameter:+value} Substitue value if parameter is not null; otherwise, substitue nothing. Supposing a script tries to dereference a null variable, a good programmer can avoid catastrophic errors byusing parameter substitution:

$ echo $NULL $ echo "Is it null? : ${NULL} :" Is it null? : :$ echo "Is it null? : ${NULL:-Nope} :"Is it null? : Nope :$ echo "Actually it still is null : ${NULL:?} :"NULL: parameter null or not set$ echo "We'll take care of that : ${NULL:=Nope} :"We'll take care of that : Nope :$ echo "Is it null? : ${NULL} :"Is it null? : Nope :

3.3 Properties of Variables

First and foremost, the shell has no concept of data types. Some programming languages such as C++ orPascal are strongly typed. In these languages, variables are grouped into type classes such as integer,character, and real. Typed variables expect a specific format for their value, and generally, they only operatewithin their class. For example, a real valued variable does not add well with a character. For the Bourneshell, there is no such restriction because every variable is simply a string of characters. This is why theresults of commands and filename expansions are as legal as typing in values by hand. For the shell, there isno concept of integers, reals, or characters. The shell does not understand that COUNT=1 means that COUNT has an integral value. It only knows that the variable holds a single character string with the ASCII value for 1.

Variables are read-write by default. Their values may be dereferenced and reset at will. If the need arises, avariable can be declared to be read-only by using the readonly command. The command takes a previouslydeclared variable and applies write protection to it so that the value may not be modified.

Bourne_Shell_Programming-Robert_P_Sayle.html file:///Z:/VMWare_Shared/Bourne_Shell_Programming-Robert_P_Say...

22 de 102 11/10/2010 16:13

$ NOWRITE="Try to change me"$ readonly NOWRITE$ echo ${NOWRITE}Try to change me$ NOWRITE="Ugh, you changed me"NOWRITE: is read only

It is important to remember that once a variable is declared read-only, from that point on, it is immutable.Therefore, it is equally important to set the variable's value before applying write protection.

The scope of variables within a script is important to consider as well. Variables defined by a shell script arenot necessarily visible to subshells the program spawns. Given a variable x defined in a shell session and given a script local executed during the same session, the value of x is undefined for script local:

$ x=World$ echo ${x}World$ cat local#!/bin/shecho "Hello ${x}"$ localHello

On the other hand, it is possible to instruct a shell session to publish its variables to all of its subshells.Called variable exporting, the technique uses the export command. Export takes a list of variables as its arguments. Each variable listed gains global scope for the current session.

$ cat local2#!/bin/shecho "${y} ${z}"$ y=Hello$ z=World$ local2 $ export y z$ local2Hello World

In the example above, variables y and z are invisible to local2 as shown by the first invocation, but afterapplying export to both variables, local2gains access to them as if it had declared them itself. Now eventhough a shell can publish its variables tosubshells, the converse does not hold. Subshells cannot promote local variables to global scope.

$ cat local3#!/bin/sha=fooexport foo$ b=bar$ echo "${a} ${b}" bar$ local3$ echo "${a} ${b}" bar

Although local3 attempts to export a to its parent shell, the active session, a does not gain visibility. Only the subshells of local3 would be able to use a. Furthermore, subshells retain a copy of exported variables.They may reassign the value of a global variable, but the reassignment is not permanent. Its effects last onlywithin the subshell. The global variable regains its original value as soon as control is returned to the parentshell.

$ cat local4#!/bin/shecho "var1 = ${var1}"var1=100echo "var1 = ${var1}"$ var1=50$ export var1$ local4

Bourne_Shell_Programming-Robert_P_Sayle.html file:///Z:/VMWare_Shared/Bourne_Shell_Programming-Robert_P_Say...

23 de 102 11/10/2010 16:13

var1 = 50var1 = 100$ echo ${var1}50

The listing of local4 shows that it prints var1's value and then assigns a value of 100 to it. Var1 is first setto 50 and then exported by the active shell. Local4 is then executed. The output shows that the variable iscorrectly exported and has its value changed to 100. At this point, the script terminates, and control returnsto the interactive session. A quick echo of var1 shows that the global instance of the variable has notchanged.

3.4 Environment Variables

The Bourne shell predefines a set of global variables known as environment variables. They define defaultvalues for the current account's home directory, the strings to use as the primary and secondary prompts, alist of directories to search through when attempting to find an executable, the name of the account, and thecurrent shell, to name a few. The values for these variables are generally set at login when the shell reads the.login

script. This script resides within the account's home directory. Environment variables are by no means set instone. They may be changed from session to session, but generally, they remain constant and keep a user'saccount configured for the system. A short listing of some common environment variables is shown in Table3.4-1.

Table 3.4-1. Common Environment Variables Variable Meaning

HOME The absolute path to the user's home directory. IFS The internal field separator characters. Usually contains space, tab, and newline. PATH A colon separated list of directories to search through when trying to execute a command. PS1PS2 The strings to use as the primary, PS1 ($), and secondary prompts, PS2 (>).

PWD The current working directory. SHELL The absolute path to the executable that is being run as the current shell session. USER The name of the current account.

Changing an environment variable is as easy as assigning it a new value. As an example, the currentdirectory could be appended to PATH's value. Doing so would allows a user to execute programs in thecurrent directory without specifying a fully qualified path to the executable.

$ echo ${PATH} /bin:/usr/bin:/usr/ucb/bin $ PATH=${PATH}:${PWD} $ echo ${PATH} /bin:/usr/bin:/usr/ucb/bin:/home/rsayle

3.5 Special Parameters

In addition to predefining environment variables, the shell accepts a set of built-in variables known asspecial parameters. There are two types. The first type provides a method for accessing a set of arguments.When a command is issued at the prompt or a function is called, it often takes a set of parameters that definewhat it operates on or how to perform its operations. The script or function retrieves this argument list byusing the positional parameters.

$ cat show-pos-params

Bourne_Shell_Programming-Robert_P_Sayle.html file:///Z:/VMWare_Shared/Bourne_Shell_Programming-Robert_P_Say...

24 de 102 11/10/2010 16:13

#!/bin/shecho ${1} ${2} ${3} ${4} ${5} ${6} ${7} ${8} ${9}$ show-pos-params testing 1 2 3 ...testing 1 2 3 ...$ show-pos-params One, two, buckle your shoe, three four shut the doorOne, two, buckle your shoe, three four shut the

There are nine positional parameters available, and each is accessible by dereferencing the argumentnumber. The script show-pos-paramslisted above demonstrates how to use the positional parameters. The example also shows that only ninearguments can be handled by a script, or a function, at any time. In the second invocation ofshow-pos-params, the tenth argument, door, does not get printed. It has not vanished and is still available tothe script; it is just not readily available via the positional parameters. This could be remedied by shifting thepositional parameters so that door moved from the tenth to the ninth position. Shifting is discussedin Section 6, Looping.

An astute reader might now ask about ${0}. Zero is a special positional parameter. It always holds the nameof the executing program. Usually, it also includes a path to the program.

$ cat show-pos-param-0#!/bin/shecho "This script's name is ${0}"$ show-pos-param-0This script's name is ./show-pos-param-0

The second set of special parameters look a little strange. They seem more like punctuation than variablesbecause each special parameter is a symbol. They are, however, extremely useful for manipulatingarguments, processes, and even the currently executing shell.

Table 3.5-1. The Special Shell Parameters Parameter Usage

$#The number of arguments passed to the shell or the number of parameters set by executing theset command.

$* Returns the values of all the positional parameters as a single value. $@ Same as $* except when double qouted it has the effect of double quoting each parameter. $$ Returns the process id of the current shell session or executing script. $! Returns the process id of the last program sent to the background. $? Returns the exit status of the last command not executed in the background. $- Lists the current options in effect (same as executing set with no arguments).

A quick example shows how these parameters behave. The succeeding chapters will demonstrate their useextensively.

$ cat show-special-params#!/bin/shecho "There are $# arguments: $*"echo Or if we needed them quoted, they would be: "$@"echo "If I wanted to backup this file, I might use ${0}.$$"echo "The last echo had exit status = $?"$ show-special-params with some argumentsThere are 3 arguments: with some argumentsOr if we needed them quoted, they would be: with some argumentsIf I wanted to backup this file, I might use ./show-special-params.2163The last echo had exit status = 0

Notice that in the second echo command, with some arguments was not printed as "with" "some" "arguments". This is a result of how echo treats quoted values; however, with some arguments is actuallypassed to echoas separately quoted values. This has the advantage of protecting the original parameter values. Withoutdouble quotes, the shell could interpret what was meant to be distinct values as one value.

Bourne_Shell_Programming-Robert_P_Sayle.html file:///Z:/VMWare_Shared/Bourne_Shell_Programming-Robert_P_Say...

25 de 102 11/10/2010 16:13

3.6 Reading Data into Variables

Data can be stored into user defined variables by assignment, as has already been shown, interactively, orfrom a file. To have a user provide a value or to get the value from a file, the read command must be used.Read

takes a list of variables as its arguments. When being used interactively, it suspends program execution andawaits user input. After the user enters some text, read scans the line of input and seequentially assigns thewords found to each of the variables in the list.

$ cat readit#!/bin/shTIC_TAC="tic tac"echo "${TIC_TAC} ?"read ANSWERecho "${TIC_TAC} ${ANSWER}"$ readittic tac ?toetic tac toe

Above, readit pauses after printing tic tac ? On the next line, the user enters toe. Read receives thevalue from standard input and stores it in the new variable ANSWER which becomes immediately usable bythe program. For read, words are delimited according to the characters defined by the internal fieldseparator variable, IFS. It is usually set to be any white space. The example shows an interactiveapplication; readmay also be used to extract values from a file. To do this, however, requires advanced knowledge of how tocombine looping constructs with I/O. This is explained later in Section 8.1, More I/O.

Continuing with read's behavior when used interactively, if there are not enough words listed in the line ofinput, the remaining variables are simply left null.

$ cat not-enough-args#!/bin/shread a b c d eecho "e = ${e}"$ not-enough-argsThere aren't enough variablese =

On the other hand, the last variable gets all the extra words if the list of variables is shorter than the availablewords.

$ cat more-than-enough-args#!/bin/shread a b cecho "c = ${c}"

$ more-than-enough-argsThere are more than enough variablesc = more than enough variables

The point here is that programmers should be aware that unexpected results can occur quite easily whenusing the read command. It is therefore important to provide rudimentary checks on a variable after readingits value. Section 5, Branching presents the tools to perform such checks.

4. Functions

Bourne_Shell_Programming-Robert_P_Sayle.html file:///Z:/VMWare_Shared/Bourne_Shell_Programming-Robert_P_Say...

26 de 102 11/10/2010 16:13

Declaring Functions1.Invoking Functions2.Properties of Functions3.Including Functions in Multiple Scripts4.

4.1 Declaring Functions

The Bourne shell allows the grouping of commands into a reusable instruction set called a function.Functions have two parts: a label and a body. The label names the function and is used to invoke thefunction. The body is the list of commands that the function executes. Here is an example function calledsetEnvironment. Its body is the three statements that set the values of the variables ROOT, LIB, and BIN.

setEnvrionment () {ROOT=${PWD}LIB=${ROOT}/libBIN=${ROOT}/bin}

There exists a few rules that must be followed in order to properly declare a function:

The function label must be unique; it cannot be the same as any other variable or other function.1.An empty set of parentheses must always follow the function label. They instruct the shell that afunction is being defined.

2.

Braces, {}, must be used to delimit the function's body.3.A function must be defined before it can be used in a script.4.

The second, third, and fourth rules are mandatory. Frankly speaking, without following them, a script willnot recognize a declaration as a function. The first one, however, is not as strict. A script can declare avariable and a function by the same name, but the results are not one what might expect:

$ FUNC="This is a variable"$ FUNC () {> echo "This is a function"> }$ echo ${FUNC}bad substitution$ FUNCThis is a function

The functional declaration of FUNCappears to be the active one, but is this always the case? If a function and a variable have the same name,will the function always be the active handle?

$ FUNC () {> echo "This is a function"> }$ FUNC="This is a variable"$ echo ${FUNC}This is a variable$ FUNCFUNC: not found

Apparently, the answer is no. Closer inspection of the examples shows that in both cases the shell uses thelast declaration. Hence, a corollary to rule one states that in the event that a variable and a function have thesame name, the most recent declaration becomes active. For the sake of completeness, the same thing can besaid about declaring two variables with the same name or two functions with the same label. The bottom lineremains: a scripter should choose unique function and variable labels.

4.2 Invoking Functions

Bourne_Shell_Programming-Robert_P_Sayle.html file:///Z:/VMWare_Shared/Bourne_Shell_Programming-Robert_P_Say...

27 de 102 11/10/2010 16:13

To use a function in a script or an interactive session, a user must define it and then call it as if it wasanother command invokable from the shell prompt.

$ cat example-func#!/bin/sh setEnvironment () { ROOT=${PWD} LIB=${ROOT}/lib BIN=${ROOT}/bin} echo "Trying to print environment..."echo ${ROOT} ${LIB} ${BIN}setEnvironmentecho "Trying to print environment again..."echo ${ROOT} ${LIB} ${BIN}$ example-funcTrying to print environment... Trying to print environment again.../home/rsayle /home/rsayle/lib /home/rsayle/bin

The fact that the parentheses following the function label is empty is a bit misleading. Normally, inlanguages such as C and C++, the parentheses delimit an argument list, and an empty set of parenthesesindicates that there are no arguments. But for the shell, the parentheses are just the method of declaration. Infact, all Bourne shell functions accept arguments. These arguments are accessible using positional parametersyntax.

$ cat func-args#!/bin/sh setEnvironment () { ROOT=${1} LIB=${ROOT}/lib BIN=${ROOT}/bin} setEnvironment /tmpecho "Trying to print environment..."echo ${ROOT} ${LIB} ${BIN}$ func-argsTrying to print environment.../tmp /tmp/lib /tmp/bin

Comparing this to the previous example, this time setEnvironment does not hard code ROOT's value. It uses the first argument passed to the function. The invocation shows the results. With /tmp acting as theparameter, setEnvironment assigns values as expected.

On the surface, shell functions appear more like procedures or routines. After all, a function generallyreturns a value. Actually, every shell function does return a value; although, it may not be readily apparentwhat the value is. Functions return the exit status of the last command executed much like when a scriptexits with no exitcommand; it uses the status of the last command issued. This similarity goes further. A script controls itsexit status by issuing an exit with a non-negative value. On the other hand, functions do not use exitbecause it is designed to terminate the shell. Instead, functions use return.

The return command stops execution of a function returning program control to the point in the script wherethe function was called. Script execution continues from where the function was invoked. The format ofreturn follows return n where nis any non-negative integer. Providing a return value is optional just as providing a status code to exit isoptional. If no code is given, return defaults to the value returned by the last command executed.

$ isADir () {> if [ ! -d ${1} ]; then> return 1> fi

Bourne_Shell_Programming-Robert_P_Sayle.html file:///Z:/VMWare_Shared/Bourne_Shell_Programming-Robert_P_Say...

28 de 102 11/10/2010 16:13

> }$ isADir ${HOME}$ echo $?0$ isADir notADir$ echo $?1

The exercise above declares the function isADir. The function checks an argument passed to it anddetermines if the argument's value represents a directory as shown by the function's first statement. If it isnot a directory, the function returns an error value of one; otherwise, it should return zero as given by theexecution of the ifstatement. The function is run twice. First with the home directory as its argument and, second, with afictitious directory name. The special parameter $? is printed in order to show the return values of each trial.

The short function just examined is a good one to demonstrate how to combine functions with testconditions. Many programmers familiar with traditional languages understand that a function can beembedded within a test in order to provide branching. The technique has the advantage of allowing multiplecommands within one line for the sake of compactness. In some cases, it can also improve programefficiency by storing the value temporarily instead of having to create a persistent variable to hold theresults. The same technique can be employed by the shell, but it is important to realize that quotes must beused.

$ if [ isADir ${HOME} ]; then> echo yep> fitest: argument expected$ if [ "isADir ${HOME}" ]; then> echo yep> fiyep

So it is perfectly legal to use functions as arguments themselves, but the syntax can become a bit unwieldy.Of course, the value can always be assigned to a variable and the variable checked, but there is a betteralternative. Certainly, the special parameter $?provides access to a function's return value. It is therefore probably best to simply execute the function as astand alone action and then immediately check the result using $?.

4.3 Properties of Functions

Aside from what has already been discussed about the behavior of functions, there are a few moreconsiderations to keep in mind. First, functions are local to the currently executing shell. A function is nevervisible to a subshell or parent shell of the script in which it is defined; in other words, there is no exportcommand for functions like there is for variables.

$ cat parent#!/bin/sh

parentFunc () { echo "This is the parent's function"}

parentFunc

echo "Calling child..."child

echo "Attempting to call childFunc from parent..."childFunc$ cat child#!/bin/sh

childFunc () { echo "This is the child's function"

Bourne_Shell_Programming-Robert_P_Sayle.html file:///Z:/VMWare_Shared/Bourne_Shell_Programming-Robert_P_Say...

29 de 102 11/10/2010 16:13

}

childFunc

echo "Attempting to call parentFunc from child..."parentFunc$ parentThis is the parent's functionCalling child...This is the child functionAttempting to call parentFunc from child..../child: parentFunc: not foundAttempting to call childFunc from parent./parent: childFunc: not found

The two scripts above demonstrate the fact that functions are local to the current shell context. Following theprogram's flow, the main shell, parent, declares a local function and calls it. The function displays a shortmessage indicating that parentFunc executed. The program continues by calling the child subscript. Childbegins execution imilarly. It also declares a local function, childFunc, which prints a message showing thatthe subshell's function ran. The subshell continues by trying to access parentFunc. Child prints the errormessage parentFunc: not foundproving that a subshell cannot use functions declared by their parents. The subshell ends, and executioncontinues within parent at the echo. At this point, the supershell attempts to call childFunc. Again, thefunction is undefined for the current shell so it is also clear that supershells cannot use functions defined bytheir subshells.

One might expect similar behavior to be true for changes to variables within functions. After all, in otherprogramming languages, variables declared in functions are local to those functions, and generally speaking,variable values passed to functions are copied into temporary storage for local manipulation. But this is notthe case. Changes made to variables persist beyond execution of the function. Moreover, variables declaredin functions gain immediate global scope.

$ cat changes#!/bin/sh changeit () { AVAR="Changed Value" NEWVAR="New Value"} AVAR="Original Value" echo "AVAR = ${AVAR}"echo "NEWVAR = ${NEWVAR}"changeitecho "AVAR = ${AVAR}"echo "NEWVAR = ${NEWVAR}"$ changesAVAR = Original ValueNEWVAR = AVAR = Changed ValueNEWVAR = New Value

When this script first prints AVAR and NEWVAR, the variables have their initial values; namely AVAR equals Original Value and NEWVAR is null. The program then runs the changeit function which resets thevariables to different values. The function ends and returns control to the main program. The same printcommands are reissued, and a quick inspection of the output shows that AVAR's value is Changed Value and NEWVALUE is suddenly defined.

To carry the examination of functional behavior even further, a third example considers nested functions. Itis quite legal to declare functions within functions. Once again, a typical programmer might guess that anested function is visible within the scope of its enclosing function. This guess is wrong. In the followingexample, the script nesting first declares afunc. Within afunc, the program defines the function nested. The script then calls nested, afunc, and nested yet again. A classical program would hide nested withinafunc; it would be expected that the output from nested would never be seen. But the results show that assoon as afunc has run, nested has become visible to the script.

Bourne_Shell_Programming-Robert_P_Sayle.html file:///Z:/VMWare_Shared/Bourne_Shell_Programming-Robert_P_Say...

30 de 102 11/10/2010 16:13

$ cat nesting#!/bin/sh afunc () { nested () { echo "This is the nested function" } echo "This is afunc"} nestedafuncnested$ nesting./nesting: nested: not foundThis is afuncThis is the nested function

As the previous three examples demonstrate, shell function behavior is atypical. Yet as has beendemonstrated, it is not unpredictable. An easy guideline to remember is that the usual scoping rules do notnecessarily apply to shell functions. Aside from this simple rule, it takes practice and careful analysis ofscripts to prevent errors caused by functions.

With all this function bashing, one might wonder why functions should be used at all? First of all, theintention is not to discourage the use of fuctions. It is just a statement of the facts. On the contrary, functionsare a very useful tool. The primary reason for using them is quite classical. Just as in every otherprogramming language, functions allow scripters to organize actions into logical blocks. It is easier to thinkof a program a series of steps to perform and then to expand those steps into functions that perform thenecessary actions. This is much better than trying to list every single command in order. The samecommands instead can be grouped into functions, and then the program calls the functions. It is infintelyeasier to program this way and to follow the script's flow.

In addition, functions can improve a script's performance. Rather than employ functions, a novice mightconsider grouping logical blocks into subscripts which the main script uses. This technique will work justfine, but the program's execution time will take a hit. When a script calls a subscript, the shell must find thesubscript on disk, open it, read it into memory, and then execute it. This process happens every time asubscript is called eventhough the subscript may have been used previously. Functions are read once intomemory as soon as they are declared. They have the advantage of one time read for multiple execution.

$ cat calls-subscript#!/bin/sh doit oncedoit twicedoit thrice$ cat doit#!/bin/sh echo "${1}"$ cat calls-function#!/bin/sh doitFunc () { echo "${1}"} doitFunc oncedoitFunc twicedoitFunc thrice

The example lists two scripts, calls-subscript and calls-function, that effectively do the same thing.Each prints the words once, twice, thrice, but the method they use to do the printing is different.Calls-subscript uses the script doit to print whereas calls-function uses the doitFunc function. TheUNIX time program can be applied to the scripts in order to see how fast each performs.

$ time calls-subscriptonce

Bourne_Shell_Programming-Robert_P_Sayle.html file:///Z:/VMWare_Shared/Bourne_Shell_Programming-Robert_P_Say...

31 de 102 11/10/2010 16:13

twicethrice real 0.2user 0.0sys 0.1$ time calls-function

4. FunctionsDeclaring Functions1.Invoking Functions2.Properties of Functions3.Including Functions in Multiple Scripts4.

4.1 Declaring Functions

The Bourne shell allows the grouping of commands into a reusable instruction set called a function.Functions have two parts: a label and a body. The label names the function and is used to invoke thefunction. The body is the list of commands that the function executes. Here is an example function calledsetEnvironment. Its body is the three statements that set the values of the variables ROOT, LIB, and BIN.setEnvrionment () { ROOT=${PWD} LIB=${ROOT}/lib BIN=${ROOT}/bin } There exists a few rules thatmust be followed in order to properly declare a function:

The function label must be unique; it cannot be the same as any other variable or other function.1.An empty set of parentheses must always follow the function label. They instruct the shell that afunction is being defined.

2.

Braces, {}, must be used to delimit the function's body.3.A function must be defined before it can be used in a script.4.

The second, third, and fourth rules are mandatory. Frankly speaking, without following them, a script willnot recognize a declaration as a function. The first one, however, is not as strict. A script can declare avariable and a function by the same name, but the results are not one what might expect: $ FUNC="This is a variable" $ FUNC () { > echo "This is a function" > } $ echo ${FUNC} bad substitution $

FUNC This is a function The functional declaration of FUNC appears to be the active one, but is thisalways the case? If a function and a variable have the same name, will the function always be the activehandle? $ FUNC () { > echo "This is a function" > } $ FUNC="This is a variable" $ echo ${FUNC} This is a variable $ FUNC FUNC: not found Apparently, the answer is no. Closer inspectionof the examples shows that in both cases the shell uses the last declaration. Hence, a corollary to rule onestates that in the event that a variable and a function have the same name, the most recent declarationbecomes active. For the sake of completeness, the same thing can be said about declaring two variables withthe same name or two functions with the same label. The bottom line remains: a scripter should chooseunique function and variable labels.

4.2 Invoking Functions

To use a function in a script or an interactive session, a user must define it and then call it as if it wasanother command invokable from the shell prompt. $ cat example-func #!/bin/sh setEnvironment() { ROOT=${PWD} LIB=${ROOT}/lib BIN=${ROOT}/bin } echo "Trying to printenvironment..." echo ${ROOT} ${LIB} ${BIN} setEnvironment echo "Trying to printenvironment again..." echo ${ROOT} ${LIB} ${BIN} $ example-func Trying to print environment... Trying to print environment again... /home/rsayle /home/rsayle/lib

/home/rsayle/bin

The fact that the parentheses following the function label is empty is a bit misleading. Normally, in

Bourne_Shell_Programming-Robert_P_Sayle.html file:///Z:/VMWare_Shared/Bourne_Shell_Programming-Robert_P_Say...

32 de 102 11/10/2010 16:13

languages such as C and C++, the parentheses delimit an argument list, and an empty set of parenthesesindicates that there are no arguments. But for the shell, the parentheses are just the method of declaration. Infact, all Bourne shell functions accept arguments. These arguments are accessible using positional parametersyntax. $ cat func-args #!/bin/sh setEnvironment () { ROOT=${1} LIB=${ROOT}/lib BIN=${ROOT}/bin } setEnvironment /tmp echo "Trying to print environment..." echo

${ROOT} ${LIB} ${BIN} $ func-args Trying to print environment... /tmp /tmp/lib /tmp/bin

Comparing this to the previous example, this time setEnvironment does not hard code ROOT's value. It uses the first argument passed to the function. The invocation shows the results. With /tmp acting as theparameter, setEnvironment assigns values as expected.

On the surface, shell functions appear more like procedures or routines. After all, a function generallyreturns a value. Actually, every shell function does return a value; although, it may not be readily apparentwhat the value is. Functions return the exit status of the last command executed much like when a scriptexits with no exitcommand; it uses the status of the last command issued. This similarity goes further. A script controls itsexit status by issuing an exit with a non-negative value. On the other hand, functions do not use exitbecause it is designed to terminate the shell. Instead, functions use return.

The return command stops execution of a function returning program control to the point in the script wherethe function was called. Script execution continues from where the function was invoked. The format ofreturn follows return n where nis any non-negative integer. Providing a return value is optional just as providing a status code to exit isoptional. If no code is given, return defaults to the value returned by the last command executed.

$ isADir () { > if [ ! -d ${1} ]; then > return 1 > fi > } $ isADir ${HOME} $ echo $? 0 $

isADir notADir $ echo $? 1 The exercise above declares the function isADir. The function checks anargument passed to it and determines if the argument's value represents a directory as shown by thefunction's first statement. If it is not a directory, the function returns an error value of one; otherwise, itshould return zero as given by the execution of the if statement. The function is run twice. First with thehome directory as its argument and, second, with a fictitious directory name. The special parameter $? isprinted in order to show the return values of each trial.

The short function just examined is a good one to demonstrate how to combine functions with testconditions. Many programmers familiar with traditional languages understand that a function can beembedded within a test in order to provide branching. The technique has the advantage of allowing multiplecommands within one line for the sake of compactness. In some cases, it can also improve programefficiency by storing the value temporarily instead of having to create a persistent variable to hold theresults. The same technique can be employed by the shell, but it is important to realize that quotes must beused.

$ if [ isADir ${HOME} ]; then > echo yep > fi test: argument expected $ if [ "isADir

${HOME}" ]; then > echo yep > fi yep

So it is perfectly legal to use functions as arguments themselves, but the syntax can become a bit unwieldy.Of course, the value can always be assigned to a variable and the variable checked, but there is a betteralternative. Certainly, the special parameter $?provides access to a function's return value. It is therefore probably best to simply execute the function as astand alone action and then immediately check the result using $?.

4.3 Properties of Functions

Aside from what has already been discussed about the behavior of functions, there are a few moreconsiderations to keep in mind. First, functions are local to the currently executing shell. A function is nevervisible to a subshell or parent shell of the script in which it is defined; in other words, there is no exportcommand for functions like there is for variables. $ cat parent #!/bin/sh parentFunc () { echo "This isthe parent's function" } parentFunc echo "Calling child..." child echo "Attempting to call childFunc fromparent..." childFunc $ cat child

Bourne_Shell_Programming-Robert_P_Sayle.html file:///Z:/VMWare_Shared/Bourne_Shell_Programming-Robert_P_Say...

33 de 102 11/10/2010 16:13

#!/bin/sh childFunc () { echo "This is the child's function" } childFunc echo "Attempting to call parentFuncfrom child..." parentFunc $ parentThis is the parent's function Calling child... This is the child function Attempting to call parentFunc fromchild... ./child: parentFunc: not found Attempting to call childFunc from parent ./parent: childFunc: notfound The two scripts above demonstrate the fact that functions are local to the current shell context.Following the program's flow, the main shell, parent, declares a local function and calls it. The functiondisplays a short message indicating that parentFunc executed. The program continues by calling the childsubscript. Child begins execution imilarly. It also declares a local function, childFunc, which prints amessage showing that the subshell's function ran. The subshell continues by trying to access parentFunc. Child prints the error message parentFunc: not found proving that a subshell cannot use functionsdeclared by their parents. The subshell ends, and execution continues within parent at the echo. At thispoint, the supershell attempts to call childFunc. Again, the function is undefined for the current shell so it isalso clear that supershells cannot use functions defined by their subshells.

One might expect similar behavior to be true for changes to variables within functions. After all, in otherprogramming languages, variables declared in functions are local to those functions, and generally speaking,variable values passed to functions are copied into temporary storage for local manipulation. But this is notthe case. Changes made to variables persist beyond execution of the function. Moreover, variables declaredin functions gain immediate global scope.

$ cat changes #!/bin/sh changeit () { AVAR="Changed Value" NEWVAR="New Value" } AVAR="Original Value" echo "AVAR = ${AVAR}" echo "NEWVAR = ${NEWVAR}" changeit echo"AVAR = ${AVAR}" echo "NEWVAR = ${NEWVAR}" $ changes AVAR = Original Value NEWVAR = AVAR

= Changed Value NEWVAR = New Value When this script first prints AVAR and NEWVAR, the variables havetheir initial values; namely AVAR equals Original Value and NEWVAR is null. The program then runs the changeit

function which resets the variables to different values. The function ends and returns control to the mainprogram. The same print commands are reissued, and a quick inspection of the output shows that AVAR's value is Changed Value and NEWVALUE is suddenly defined.

To carry the examination of functional behavior even further, a third example considers nested functions. Itis quite legal to declare functions within functions. Once again, a typical programmer might guess that anested function is visible within the scope of its enclosing function. This guess is wrong. In the followingexample, the script nesting first declares afunc. Within afunc, the program defines the function nested. The script then calls nested, afunc, and nested yet again. A classical program would hide nested withinafunc; it would be expected that the output from nested would never be seen. But the results show that assoon as afunc has run, nested has become visible to the script.

$ cat nesting #!/bin/sh afunc () { nested () { echo "This is the nested function" } echo "This is afunc" } nested afunc nested $ nesting ./nesting: nested: not found

This is afunc This is the nested function

As the previous three examples demonstrate, shell function behavior is atypical. Yet as has beendemonstrated, it is not unpredictable. An easy guideline to remember is that the usual scoping rules do notnecessarily apply to shell functions. Aside from this simple rule, it takes practice and careful analysis ofscripts to prevent errors caused by functions.

With all this function bashing, one might wonder why functions should be used at all? First of all, theintention is not to discourage the use of fuctions. It is just a statement of the facts. On the contrary, functionsare a very useful tool. The primary reason for using them is quite classical. Just as in every otherprogramming language, functions allow scripters to organize actions into logical blocks. It is easier to thinkof a program a series of steps to perform and then to expand those steps into functions that perform thenecessary actions. This is much better than trying to list every single command in order. The samecommands instead can be grouped into functions, and then the program calls the functions. It is infintelyeasier to program this way and to follow the script's flow.

In addition, functions can improve a script's performance. Rather than employ functions, a novice mightconsider grouping logical blocks into subscripts which the main script uses. This technique will work justfine, but the program's execution time will take a hit. When a script calls a subscript, the shell must find the

Bourne_Shell_Programming-Robert_P_Sayle.html file:///Z:/VMWare_Shared/Bourne_Shell_Programming-Robert_P_Say...

34 de 102 11/10/2010 16:13

subscript on disk, open it, read it into memory, and then execute it. This process happens every time asubscript is called eventhough the subscript may have been used previously. Functions are read once intomemory as soon as they are declared. They have the advantage of one time read for multiple execution.

$ cat calls-subscript #!/bin/sh doit once doit twice doit thrice $ cat doit #!/bin/sh echo "${1}" $ cat calls-function #!/bin/sh doitFunc () { echo "${1}" } doitFunc once

doitFunc twice doitFunc thrice The example lists two scripts, calls-subscript and calls-function,that effectively do the same thing. Each prints the words once, twice, thrice, but the method they use to dothe printing is different. Calls-subscript uses the script doit to print whereas calls-function uses the doitFunc function. The UNIX timeprogram can be applied to the scripts in order to see how fast each performs. $ time calls-subscript oncetwice thrice real 0.2 user 0.0 sys 0.1 $ time calls-function once twice thrice real 0.0user 0.0 sys 0.0 The script that uses a function is faster. It is not even measurable by the system.On the other hand, calls-subscript executes on the order of a couple tenths of a second.

4.4 Including Functions in Multiple Scripts

The Bourne shell supports a construct similar to the #include macro found in C/C++ or the import commandof Pascal. Called the dot command, a period instructs the shell to read the file named and to execute it as ifthe commands contained had been entered at that point. In other words, the shell treats the included file aspart of the currently executing program. In shell terminology, this construct is called sourcing. The namedfile is sourced so that its environment and commands become an inherent part of the running shell.

$ cat checkDir

checkDirFunc () { if [ ! -d ${1} ]; then echo "${1} is not a directory"; exit 1 fi }

$ cat checkHome

#!/bin/sh

. checkDir

checkDirFunc echo "Of course it's a directory!"

$ checkHome ${HOME} Of course it's a directory! $ checkHome notAdir notAdir is not a directory

The example above uses two scripts: checkDir and checkHome. Notice that checkDir does not contain the #!/bin/sh

directive. It is not meant to be run as a stand-alone program. (This does not prevent the file from beingexecuted if called by a running shell. The script will simply be run within the shell. The only way to trulyprevent it from being executed is to set the correct file permissions such that it is not an executable file.) Onthe other hand, checkHome is written to execute within its own shell. Moreover, checkHome sourcescheckDir as can be seen by the line . checkDir. By sourcing the checkDir file, checkHome then containsthe checkDirFunc function which tests the first positional parameter to see if it is a directory. If it is not adirectory, the if statement prints a message stating the fact and exits the program. If it is a directory,program execution continues past the function to the final echo statement letting the user know that the argument is a directory. The operation of the entire script is shown by the two examples of running thescript against the user's home directory and then against the bogus string notAdir.

Bourne_Shell_Programming-Robert_P_Sayle.html file:///Z:/VMWare_Shared/Bourne_Shell_Programming-Robert_P_Say...

35 de 102 11/10/2010 16:13

There is one final comparison that can be made here. The previous subsection discusses the advantage ofusing functions versus calling subscripts. Naturally, one might ask how sourcing functions compares tothese. Consider three methods of counting to 1000. The first method declares a function calledprintCount:

$ cat calls-function #!/bin/sh

COUNT=1

printCount () { echo ${COUNT} }

until [ ${COUNT} -gt 999 ]; do printCount COUNT=`expr ${COUNT} + 1` done

echo "`basename ${0}`: Done counting to 1000"

In the script calls-function, the printCount function simply displays the current value of COUNT. Anuntil loop iincrements and monitors the value until it reaches a thousand by determining whether COUNT is greater than 999. At that point, the program terminates with a short message that it finished counting. Timing the script's execution yields the following results. (Note that the actual output has been trimmed tothe results of time in the interest of saving space.)

$ time calls-function calls-function: Done counting to 1000 4.86user 6.57system 0:11.68elapsed

Now consider the second method of counting to 1000. Here, the script sources-function imports the samefunction, printCount, from the file function; it sources the code used to print the value of COUNT.

$ cat sources-function #!/bin/sh

COUNT=1

. function

until [ ${COUNT} -gt 999 ]; do printCount COUNT=`expr ${COUNT} + 1` done

echo "`basename ${0}`: Done counting to 1000" $ cat function

printCount () { echo ${COUNT} }

Once again, an until loop watches and terminates once COUNT reaches 1000. Comparing the execution timeto calls-function, it can be seen that there is a slight performance penalty for sourcing. This can beexplained by the fact that the file function, when it is sourced, has to be opened and its contents committedto memory before the function can be used. On the other hand, in calls-function the shell readsprintFunction during its initial pass over the contents of the script. It does not incur the penalty of havingto do the extra file read that sources-function does.

Bourne_Shell_Programming-Robert_P_Sayle.html file:///Z:/VMWare_Shared/Bourne_Shell_Programming-Robert_P_Say...

36 de 102 11/10/2010 16:13

$ time sources-function sources-function: Done counting to 1000 4.96user 6.61system 0:11.80elapsed

Finally, compare the first two methods to calls-subscript. This last script exports COUNT and then uses the program subscript to display COUNT's value:

$ cat calls-subscript #!/bin/sh

COUNT=1 export COUNT

until [ ${COUNT} -gt 999 ]; do subscript COUNT=`expr ${COUNT} + 1` done

echo "`basename ${0}`: Done counting to 1000" $ cat subscript #!/bin/sh

echo ${COUNT}

Running calls-subscript through the time program shows:

$ time calls-subscript calls-subscript: Done counting to 1000 10.95user 13.37system 0:24.83elapsed

In this last example, there is a significant increase in the program's performance. It takes roughly twice aslong to complete as the first two. This can be attributed to the fact that each time calls-subscript usessubscript, the shell must open the file, spawn a subshell, execute the echo statement, and then return control back to the main program.

From this exercise it is plain to see that including function code directly into scripts is the most optimal. It isdefinitely a bad idea to divide scripts into many subscripts because of the performace penalty. Try to usefunctions whenever it seems like a subscript fits. Now there is something to be said for sourcing commandsfrom another file. Sourcing does not hinder program execution much and it does allow the organization ofreusable code blocks into script libraries. Still, these same code blocks could simply be copied directly intoa script's body in the name of speed. But that is a matter of choice. After all, the enhancement of speedcould come at the cost of readability.

5. BranchingTesting Conditional Statements1.If Statements2.Case Statements3.

5.1 Testing Conditional Statements

Decisions, decisions, decisions. One of the corner stones of every programming language is the ability to

Bourne_Shell_Programming-Robert_P_Sayle.html file:///Z:/VMWare_Shared/Bourne_Shell_Programming-Robert_P_Say...

37 de 102 11/10/2010 16:13

make decisions. A decision can only be made based upon a proposition. In other words, if a proposition istrue or false, then take appropriate action. Learning to evaluate a proposition in the shell requires mastery ofthe testcommand. Taking action based upon the proposition's result is discussed in the subsections and chaptersthat follow.

The built-in command testevaluates logical expressions. It takes boolean statments as its argument and returns a value of zero or onemeaning true and false, respectively. Looking at the statements below, for example, test can be used tocompare the values of two constants.

$ test 100 -eq 100 $ echo $? 0 $ test 100 -eq 50 $ echo $? 1

In the first case, the user checks if 100 is equal to 100. Note that test does not actually return the valuedirectly to the user. Instead the user pulls the result from the $? special parameter. As can be seen, theconstants are indeed identical since 0was returned. The second case shows test returning a false value. Here the constants 100 and 50 are not equal so test returns 1.

There is a shortened form of the test command. Enclosing an expression in square brackets, [], also tests thestatement. To use them, place the expression between the brackets. Be certain to include white spacebetween the brackets and the statement.

$ [ ${HOME} = /home/rsayle ] $ echo $? 0

The white space is mandatory for the shell to properly interpret the expression. Notice how the shell willmisread the test condition as an executable file if the space is missing between the opening bracket and thefirst operand:

$ [${HOME} = /home/rsayle ] sh: [/home/rsayle: No such file or directory

Also notice that it does not find the test's termination point without the space between the final operand andthe closing bracket:

$ [ ${HOME} = /home/rsayle] [: missing `]'

Always prefer brackets over the test command; it makes scripts easier to read.

There are a number of operators that allow the formation of logical expressions for testing. Being that theshell itself operates primarily on strings, it follows, naturally, that there exist string operators. The shell cantest a string to see if it is empty or non-empty or it can compare two strings for equivalence.

Bourne_Shell_Programming-Robert_P_Sayle.html file:///Z:/VMWare_Shared/Bourne_Shell_Programming-Robert_P_Say...

38 de 102 11/10/2010 16:13

Table 4.1-1. String OperatorsConstruct Meaningstring string is not null

-n string string is not null-z string string is null

string1 = string2 string1 is identical to string2string1 != string2 string1 is not identical to string2

Bourne_Shell_Programming-Robert_P_Sayle.html file:///Z:/VMWare_Shared/Bourne_Shell_Programming-Robert_P_Say...

39 de 102 11/10/2010 16:13

So here are some farily simple examples that demonstrate these operators.

$ [ "hello" = "world" ] $ echo $? 1 $ ASTRING="foo" $ [ ${ASTRING} ] $ echo $? 0 $ NULLSTRING="" $ [ ${NULLSTRING} ] $ echo $? 1 $ [ -n "${NULLSTRING}" ] $ echo $? 1 $ [ -z "${NULLSTRING}" ] $ echo $? 0 $ [ -z ${NULLSTRING} ] test: argument expected

The first test checks the equivalence of the strings hello and world using the = operator. The result showsthat the strings are not the same. Then, the operator assigns a variable, ASTRING, the value foo. The userencloses the value in test brackets and retrieves the result. This construct tests the string for emptiness. If ithas no value or is an empty string, denoted by "", test returns false. As long as it has some value, thestring is considered non-empty, and test returns true. So testing ASTRING yields 0. On the other hand, theoperator then declares an empty string, NULLSTRING, and checks it. This time test returns false. Nowcompare this to using -n which returns true if a string is not empty. The -n operator gives the same result,false, as shown by the fourth test. And just for fun, the user follows this by demonstrating the -z operator. Here, -z reports a true result because NULLSTRING in fact an empty string.

Finally, notice the potential problem of testing strings. In the last command, the user passes the variable'svalue unquoted to the operator. Because the variable really has no value, the shell passes no argument totest. This results in an error. It is extremely important to get in the habit of quoting string variables. Novicescripters often assign values to variables by storing the result of a command. The variable's value is thendereferenced and parsed. If the command yields no value, then the variable is null. Any tests or commandsperformed on the null value result in an error and program termination. By quoting the variable during itsdereference the shell treats it as an empty string instead of nothing at all, and in most cases, the programdoes not crash.

Testing strings alone would make for a fairly inflexible system. The shell does contain facilities for testingother conditions. In fact, it can perform some routine integer comparisons; namely, the shell supports:

-eq equal to,-ne not equal to,-gt greater than,-ge greater than or equal to,-lt less than, and-le less than or equal to.

These operators function the same as in any other programming language. They compare the left operand tothe right operand and return a true or false value. As a quick example, here is a comparison between thevariable TEN, whose value is 10, with various other values.

$ TEN=10

Bourne_Shell_Programming-Robert_P_Sayle.html file:///Z:/VMWare_Shared/Bourne_Shell_Programming-Robert_P_Say...

40 de 102 11/10/2010 16:13

$ [ ${TEN} -eq 10 ] $ echo $? 0 $ [ ${TEN} -gt 10 ] $ echo $? 1 $ ONEHUNDRED=100 $ [ ${TEN} -le ${ONEHUNDRED} ] $ echo $? 0 In addition to string and integral comparisons, the shell supports a vast array of file operators. One can testfor the existence of a file, if the file is readable, writeable, or even executable, and for the file type. The fulllist of file checking capabilities is given in the table below. It should be noted that having built-in filetesting is quite a natural extension of the shell. After all, the basic unit within UNIX is the file. Data,directories, hardware interfaces, I/O devices, and even network communications are represented by files. Because of this, the ability to manipulate files is key to proper system operation. This book has alreadydiscussed some file handling techniques including I/O redirection and the representation of scripts in thesystem. It now adds the built-in file testing facilities.

Table 4.1-2. File OperatorsOperator Returns true if...

-b file is a block special file-c file is a character special file-d file is a directory-f file is an ordinary file-g file has its set group ID (SGID) bit set-k file has its sticky bit set-p file is a named pipe-r file is readable by the current process-s file has a non-zero length-t file descriptor is open and associated with a terminal-u file has its set user id (SUID) bit set-w file is writable by the current process-x file is executable by the current process

All of the file operators shown are unary. They only take a single file as an argument. To test multipleconditions on a file, a scripter must string together a list of expressions using the logical conjuctions, whichare discussed below. Also, to negate an operator, simply precede the expression with an exclamation point.

$ ls -l total 42 drwxr-xr-x 2 rsayle users 5120 Apr 11 17:13 complete/ -rwxr-xr-x 1 rsayle users 313 Apr 11 17:13 cpusers* -rw-r--r-- 1 rsayle users 1244 Apr 11 17:13 goad-s.txt -rw-r--r-- 1 rsayle users 66 Apr 11 17:13 helco3.txt -rw-r--r-- 1 rsayle users 103 Apr 11 17:13 helco3tx.txt -rw-r--r-- 1 rsayle users 1246 Apr 11 17:13 lawrenc-c.txt -rw-r--r-- 1 rsayle users 1243 Apr 11 17:13 lien-v.txt -rw-r--r-- 1 rsayle users 1244 Apr 11 17:13 magro-j.txt

Bourne_Shell_Programming-Robert_P_Sayle.html file:///Z:/VMWare_Shared/Bourne_Shell_Programming-Robert_P_Say...

41 de 102 11/10/2010 16:13

-rw-r--r-- 1 rsayle users 1244 Apr 11 17:13 mergard-j.txt -rwxr-xr-x 1 rsayle users 7048 Apr 11 17:13 mkconfig* -rw-r--r-- 1 rsayle users 1244 Apr 11 17:13 mynes-c.txt -rw-r--r-- 1 rsayle users 1243 Apr 11 17:13 nair-h.txt -rw-r--r-- 1 rsayle users 1244 Apr 11 17:13 pricket-j.txt -rw-r--r-- 1 rsayle users 1241 Apr 11 17:13 riely-c.txt -rwxr-xr-x 1 rsayle users 1444 Apr 11 17:13 setamdroute* -rw-r--r-- 1 rsayle users 1244 Apr 11 17:13 short-j.txt -rw-r--r-- 1 rsayle users 1244 Apr 11 17:13 spilo-d.txt -rw-r--r-- 1 rsayle users 334 Apr 11 17:13 users -rw-r--r-- 1 rsayle users 1243 Apr 11 17:13 zein-a.txt $ [ -d complete ] $ echo $? 0 $ [ -d cpusers ] $ echo $? 1 $ [ -x mkconfig ] $ echo $? 0 $ [ -f users ] $ echo $? 0 $ [ ! -c users ] $ echo $? 0

The previous example demonstrates the usage of the file operators. It begins with a detailed listing of thecurrent directory. Comparing this to the tests shows how a few of the operators work. The first test checkscomplete

to see if it is a directory. Looking at the file listing and then checking the returned result shows that it isindeed a directory; however, the script cpusersis not a directory as the next test proves. Next, the example shows that the mkconfig script is executable byusing -x. Then the users file is first shown to be a plain text file with -f. The example ends with andemonstration of how to take the complement of a file operator. Notice that by entering ! -c (read "not -c")the test verifies that usersis not a character special device. This is a true statement as both the result and the preceding test on usersshow.

Now in order to build more meaningful test conditions, it may be necessary to join two or more expressions. The shell achieves this by providing logical AND, -a, and OR, -o. Once again, the usual rules of logictaught in math classes around the globe apply. In the case of -a, both conditions surrounding theconjunction must be true for test to return a true value. For -o, either expression may be true for test toreturn true. To use -a or -o, simply place the conjunction between the boolean statements. Returning to theprevious example of comparing files:

$ [ -f setamdroute -a -x setamdroute ] $ echo $? 0 $ [ -c users -o -d complete ] $ echo $? 0 $ [ -c users -a -d complete ] $ echo $? 1

Note that any combination of file, integral, or string comparisons can be joined by -a and -o, but for

Bourne_Shell_Programming-Robert_P_Sayle.html file:///Z:/VMWare_Shared/Bourne_Shell_Programming-Robert_P_Say...

42 de 102 11/10/2010 16:13

demonstration, this example sticks to testing files. So the first test checks that setamdroute is an ordinarytext file and that it is executable. Since both statements are true, the result is true. The example continuesby verifying that either users is a character special file, which it is not, or that complete is a directory. Since complete is a directory, testreturns true. Finally, the test now changes to an AND expression to show that both conditions must be true;otherwise, test returns false.

As a final discussion of test, a good scripter must know how to force a desired condition. Namely, set thevalue of the expression so that the test always returns true or false as desired. Most UNIX machines havetwo commands that do exactly this. They are aptly named true and false. Whenever a condition shouldbe forced, these commands can be substituted in place of an actual test. For example, a system administratormight want to monitor a printing queue. Being a master scripter, said administrator would initiate a whileloop that calls the lpq command every second so that he can watch for jobs. In order to keep the while looprunning, he passes the true command to the loop.

$ while true > do > lpq -Plp; sleep 1 > done no entries no entries no entries ^C

Another method of forcing a condition is to use the null command. Represented by a colon (:), the null command is a do nothing statement that the always returns zero. It can be used in place of a test conditionlike true and false. It may also be used as an empty statement where the shell expects something, but theuser does not necessarily want any processing to occur. This could be useful, for example, duringdevelopment of a branch when a user might just want to test the condition before determining the commandblock that executes.

$ while : > do > lpq -Plp; sleep 1 > done no entries no entries no entries ^C

So there is the same print queue monitoring script entered by the now lazy system administrator who figuresthat saving the extra few key strokes means a shorter trip to carpal-tunnel hell. Actually, this is a verycommon technique. It may not be as readable as the word true, but it gets the job done. And here is anexample of where a scripter might not yet know what command set should be executed after a certain test issatisfied.

if [ "${USER}" = "root" ]; then : else echo "`basename ${0}`: you must be root to run this program." exit 1 fi

The if statement makes sure that the user is the root user. The scripter may not know exactly what steps totake once this is verified, but for quick testing, it is left blank with a :. With this in place, the user canactually run the script to ensure that if the user is not root, then the program exits with an error.

Bourne_Shell_Programming-Robert_P_Sayle.html file:///Z:/VMWare_Shared/Bourne_Shell_Programming-Robert_P_Say...

43 de 102 11/10/2010 16:13

5.2 If Statements

Now that it has been shown how to make decisions, it is time to learn how to act based upon its result. Thisis called branching. The first branching method uses the if-then-elif-else-fi construct. Read elif as "else if;" it makes more sense. An ifstatement reads similarly to classical logic. If something is true, then do the following, or if anotherstatement is true, then do this instead; otherwise, just do this. Sentences that can be written in this fashioncan be easily translated into an if block.

The if-then-elif-else-fi block has the following syntax:

if [ condition ]; then command1 : commandn elif [ condition ]; then command1 : commandn : : elif [ condition ]; then command1 : commandn else command1 : commandn fi

This construct may appear to be long-winded, but it is actually pretty simple to analyze. The ifs and elifs are pretty much the same. An if or elif is followed by a test. Whenever the test evaluates to true, the shellexecutes the commands that follow and then continues with the rest of the script after the block's end point, the fi. The else statement is a default action. Given that neither of the if or elifs pass, the shell performs the commands within the else block, and the script again continues execution after the fi.

Usage of elifs and elses in an if statement are optional. As a bare minimum, an if block must utilize anif-then-fi construct. Here is a popular example:

if [ "${USER}" != "root" ]; then echo "Sorry, ${USER}, you must be the superuser to run this program." exit 1 fi echo "Executing program."

This simple little subroutine checks if the current operator is the root user. If not, then it prints a messageindicating that the script may only be executed by those with root privileges and promptly terminates. In theevent that the operator is logged in as root, the script moves to the fi and then prints a friendly message indicating that it is continuing execution.

A more advanced example might look like:

if [ -x ${FILE} ]; then ${FILE} elif [ -w ${FILE} ]; then vi ${FILE}

Bourne_Shell_Programming-Robert_P_Sayle.html file:///Z:/VMWare_Shared/Bourne_Shell_Programming-Robert_P_Say...

44 de 102 11/10/2010 16:13

else echo "${FILE} is neither executable nor writable; don't know what to do!" exit 1 fi

Given a file, if the file is executable, the first part of the block causes the shell to run the program. But if itis not executable, the block continues by testing it for readability. If it turns out FILE is writable, it loads it into the vi editor. Should both tests fail, the block writes an error message and terminates the script.

If blocks can be nested. In other words, it is perfectly legal to build if statements within ifs, elifs, or even elseblocks. Nesting, however, should only be used as an absolute necessity. Nested blocks are difficult to readwhich, consequently, makes them harder to debug. Try to rewrite nested if blocks as a series of ifs and elifs that perhaps have more precise test conditions.

5.3 Case Statements

The second form of branching available is the case statement. A case differs from an if block in that it branches based upon the value of one variable. An if does not have such restrictions because it uses thetest function, and a testcan be any string of logical expressions as has been shown. The best way to illustrate this difference is toexamine the syntax:

case (variable) in pattern1) command1 : commandn ;; : patternn) command1 : commandn ;; esac

The case block takes as its argument a variable. To denote the variable, it must be surrounded byparentheses. The case compares the variable's value against each pattern. The pattern may be any legalregular expression. If the variable's value matches the pattern, then the shell executes the command blockimmediately following the pattern. The command block terminates with a pair of double semi-colons. Assoon as it reaches them, the shell continues past the end of the case block as denoted by the esac.

A good use for a case statement is the evaluation of command line arguments:

option="${1}" case ${option} in -f) FILE="${2}" ;; -d) DIR="${2}" ;; *) echo "`basename ${0}`: usage: [ -f filename ] | [ -d directory ]" exit 1 ;; esac

Bourne_Shell_Programming-Robert_P_Sayle.html file:///Z:/VMWare_Shared/Bourne_Shell_Programming-Robert_P_Say...

45 de 102 11/10/2010 16:13

In this example, the script expects two arguments. The first is one of two option indicators, -f or -d, that tell the program whether the second argument is a file or a directory. The case statement switches upon the first argument's value stored in option. If option equals -f, then it assigns the option's argument, thesecond positional parameter, to FILE. Then the scripts continues after the esac. On the other hand, ifoption is -d, the shell assigns the second argument's value to DIR and continues execution. Finally, thiscase statement has a default action. In the event the user does not use either the -f or -d options, it detects the error with the wild card, *, which matches any pattern. The script prints a message instructing the userhow to use it properly, and then exits.

Remember that a caseswitches against patterns. Programmers who can build regular expressions with ease should have noproblem designing effective case blocks.

6. LoopingFor Loops1.While Loops2.Until Loops3.Loop Control4.

6.1 For Loops

With functions, variables, branching, and soon looping under the proverbial belt, all the tools to createuseful shell scripts are at hand. But first, one must understand how to build and control shell loops. A looprepeats a command block based upon a set of criteria that determines when to continue or terminate theloop. Loops are a hallmark of all programming languages because they allow computers to do with greatprecision and speed what humans are not so terribly great at: repeating actions.

In the Bourne shell, the first type to consider is the for loop. A for loop processes multiple commands on alist of items. This is a bit non-traditional in that languages such as C and FORTRAN employ for loops as asort of counting mechanism; the loop continues until the variable reaches a target value. But here, the forloop uses the intermediate variable to execute actions on a set of items such as files or the results of acommand. In this way, the shell's for loop is a more flexible and useful than in most other languages.

The syntax of a for loop is:

for variable in list do command1 : commandn done

The for loop's variablemay be any legal shell variable. It need not be declared prior to the loop, and in fact, good shellprogramming practice mandates that the scripter should choose a unique variable that is not and will not beused elsewhere. This is punctuated by the fact that the variable does become global in the script. As the forloop executes the command block, the value of variable changes into the next item found in list. Items inlist must be separated by white space (space, tab, or newline characters). The list may be generated bymany different methods including: file expansion, variable substitution, positional parameters, commandsubstitution, or by simply providing a list of strings immediately following the in statement.

Bourne_Shell_Programming-Robert_P_Sayle.html file:///Z:/VMWare_Shared/Bourne_Shell_Programming-Robert_P_Say...

46 de 102 11/10/2010 16:13

$ for file in ./* > do > file ${file} > done ./bin: directory ./ftp: directory ./majordomo.txt: English text ./mig29.ps: PostScript document ./ph.dat: ascii text ./prologues: directory ./source: directory

Here, the for loop takes advantage of filename expansion. The ./* directs the loop to process all of the files located in the current directory. The intermediate variable used is named file. Its values are passed tothe filecommand. This command gives a best guess description of what type of file the argument is. Todifferentiate the file command from the intermediate variable, the variable of course is surrounded by curlybraces. As for the results, bin, ftp, prologues, and source are all directories; majordomo.txt isconsidered to be prose; mig29.ps is a PostScript document; and ph.dat is a plain ASCII file.

The same results can be achieved using command substitution. Surely, by using ls in the working directory,the loop processes the same arguments:

$ for file in `ls` > do > file ${file} > done ./bin: directory ./ftp: directory ./majordomo.txt: English text ./mig29.ps: PostScript document ./ph.dat: ascii text ./prologues: directory ./source: directory

In fact, the commands passed to a forloop as a list can be as simple or complex as a scripter wishes; however, for readability and for ease ofdebugging, a programmer should consider assigning the command's output to a variable and then passing thevariable's value as the loop's argument list.

$ NOPASSWD=`ypcat passwd | grep "^[a-zA-Z0-9]*::" | cut -f1 -d:` $ for user in ${NOPASSWD} > do > mail ${user} << END > Please set your password. You currently don't have one! > END > done

Here's a nice hackers' example, or perhaps a tool for a security conscious system administrator. The scriptbegins by assigning to NOPASSWDthe results of a rather lengthy command filter. The command passes the NIS password database to grep. Grep

searches for any entry without a password. The passwd database consists of a set of fields delimited bycolons. The second field is the account's encrypted password; hence, an account without a password wouldbe denoted by an entry with a double colon immediately following the account name. The list grepgenerates is then passed to cutto parse out the offending account names from the database entry. After this building the list, thesubsequent for loop sends an email to each user warning them that they must correct the security problem

Bourne_Shell_Programming-Robert_P_Sayle.html file:///Z:/VMWare_Shared/Bourne_Shell_Programming-Robert_P_Say...

47 de 102 11/10/2010 16:13

they created.

For the last consideration of for loops, the in list portion is optional. If omitted, the loop uses thepositional parameters as its arguments by default. This is equivalent to writing, "for var in "$@" do ..." This technique should be used with care because it is not always immediately obvious to anotherreading the script what the arguments of the loop are.

$ cat printargs #!/bin/sh for arg do echo ${arg} done $ printargs foo bar * foo bar bin printargs

This simple example quickly illustrates the concept. The user executes the printargs script which echos the arguments back to the console. The arguments are the strings foo and bar as well as the contents of thecirrent directory as given by the shell's file expansion of *.

6.2 While Loops

A while loop is different from a forloop in that is uses a test condition to determine whether or not to execute its command block. As long as thecondition returns true when tested, the loop executes the commands. If it returns false, the script skips theloop and proceeds beyond its termination point. Consequently, the command set could conceiveably neverrun. This is further illustrated by the loop's syntax:

while condition do command1 : commandn done

Because the loop starts with the test, if the condition fails, then the program continues at whatever actionimmediately follows the loop's donestatement. If the condition passes, then the script executes the enclosed command block. After performingthe last command in the block, the loop tests the condition again and determines whether to proceed beyondthe loop or fall through it once more.

$ i=1 $ while [ ${i} -le 5 ] > do > echo ${i} > i=`expr ${i} + 1` > done 1 2 3 4 5

Bourne_Shell_Programming-Robert_P_Sayle.html file:///Z:/VMWare_Shared/Bourne_Shell_Programming-Robert_P_Say...

48 de 102 11/10/2010 16:13

The preceeding example demonstrates a while loop. Given the variable i whose initial value is one, the loop starts by checking if i is less than or equal to five. Since it is, the loop prints i's value, increments it by one as shown with the expr command, and then tests its value once more. This continues until i reachessix. At that point, the loop terminates, and the shell returns control to the user.

The built-in command shift works well with while loops. Shift causes the positional parameters' values toleft-shift by its argument. Shiftcan take any non-negative value as its argument and reset the positional parameters accordingly. By default,shift

moves the arguments by one. When the parameters are shifted by one, the first is dropped from the list, thesecond parameter's value becomes that of the first, and third's becomes the second, and so on up to the ninthparameter. This technique can be used effectively with a while loop for processing script options orfunction arguments.

$ cat printargs #!/bin/sh while [ $# -gt 0 ] do echo "$@" shift done $ printargs hello world "foo bar" bye hello world foo bar bye world foo bar bye foo bar bye bye

The printargs script combines a while loop with the shift command. The script takes an argument listand prints the entire list. After doing so, it then shifts the arguments by one and repeats the process until itshifts the arguments out of existence. The example demonstrates this with a four argument list of words. The third argument is two words passed as one variable by enclosing them in double-quotes.

6.3 Until Loops

Until loops are the other while loops. They work similarly in nature to while loops except for one majordifference. An until loop executes its command block only if the test condition is false. In this sense, anuntil loop can be viewed as the logical negation of a while loop.

until condition do command1 : commandn done

Once again, the command block may never be executed as shown in its syntax. If the condition evaluates totrue, then the script proceeds beyond the loop.

To demonstrate, the example below shows how to rewrite the printargs script above changing the while to an until loop:

$ cat printargs2 #!/bin/sh until [ $# -le 0 ] do echo "$@"

Bourne_Shell_Programming-Robert_P_Sayle.html file:///Z:/VMWare_Shared/Bourne_Shell_Programming-Robert_P_Say...

49 de 102 11/10/2010 16:13

shift done $ printargs2 hello world "foo bar" bye hello world foo bar bye world foo bar bye foo bar bye bye

6.4 Loop Control

Sometimes it is necessary to end a loop before the test condition is satisfied or a list is completelyprocessed. In such cases, the scripter uses the break command. When encountered, the break command instructs the shell to continue processing beyond the enclosing loop. In other words, script execution picksup just after the done statement.

$ ls -l total 4 -rw-r--r-- 1 rsayle users 128 Aug 16 23:00 catfiles -rw-r--r-- 1 rsayle users 48 Aug 16 23:01 exrc drwxr-xr-x 2 rsayle users 1024 Aug 16 23:02 include/ -rw-r--r-- 1 rsayle users 89 Aug 16 23:02 profile $ cat catfiles #!/bin/sh for file in ./* do if [ ! -d ${file} ]; then echo "${file}:" cat ${file} echo else break fi done echo echo "End of catfiles"

For this example, the current working directory contains four objects: the script catfiles, an exrc file containing the user's vi defaults, an include directory holding a users header files, and a profilecontaining the user's shell default environment. Looking at catfiles, the script runs a for loop against allfiles in the working directory. If the file is not a directory, the script prints a line with the file's namefollowed by a colon. Then the program cats the file printing its contents to the screen. If the file is adirectory, however, the script breaks the for loop and prints a termination message before exiting. Runningcatfiles against the directory produces:

$ catfiles ./catfiles: #!/bin/sh for file in ./* do if [ ! -d ${file} ]; then echo "${file}:" cat ${file} else break fi

Bourne_Shell_Programming-Robert_P_Sayle.html file:///Z:/VMWare_Shared/Bourne_Shell_Programming-Robert_P_Say...

50 de 102 11/10/2010 16:13

done

./exrc: set number autoindent tabstop=2 showmode nowrap

End of catfiles $

The files, as listed by the previous lscommand, are processed in order. As the script encounters each file before the include directory, it cats the file. Once it hits include, the script completes leaving the contents of profile a mystery.

Had the programmer intended to view all files except directories, the programmer would have used thecontinue command instead. When the shell encounters a continue it skips the current processing step andproceeds with the loop's next iteration. The loop does not just quit as it does with break. It just executes thenext trial until the test condition is satisfied or the argument list is exhausted.

Taking a look at catfiles using a continue instead of a break shows the difference. This time the scriptsteps past the include directory and dumps the contents of profile.

$ catfiles ./catfiles: #!/bin/sh for file in ./* do if [ ! -d ${file} ]; then echo "${file}:" cat ${file} else continue fi done

./exrc: set number autoindent tabstop=2 showmode nowrap

./profile: # setup terminal characteristics export TERM=vt100 set -o vi # turn off mess

6. LoopingFor Loops1.While Loops2.Until Loops3.Loop Control4.

6.1 For Loops

With functions, variables, branching, and soon looping under the proverbial belt, all the tools to createuseful shell scripts are at hand. But first, one must understand how to build and control shell loops. A looprepeats a command block based upon a set of criteria that determines when to continue or terminate theloop. Loops are a hallmark of all programming languages because they allow computers to do with great

Bourne_Shell_Programming-Robert_P_Sayle.html file:///Z:/VMWare_Shared/Bourne_Shell_Programming-Robert_P_Say...

51 de 102 11/10/2010 16:13

precision and speed what humans are not so terribly great at: repeating actions.

In the Bourne shell, the first type to consider is the for loop. A for loop processes multiple commands on alist of items. This is a bit non-traditional in that languages such as C and FORTRAN employ for loops as asort of counting mechanism; the loop continues until the variable reaches a target value. But here, the forloop uses the intermediate variable to execute actions on a set of items such as files or the results of acommand. In this way, the shell's for loop is a more flexible and useful than in most other languages.

The syntax of a for loop is:

for variable in list do command1 : commandn done

The for loop's variablemay be any legal shell variable. It need not be declared prior to the loop, and in fact, good shellprogramming practice mandates that the scripter should choose a unique variable that is not and will not beused elsewhere. This is punctuated by the fact that the variable does become global in the script. As the forloop executes the command block, the value of variable changes into the next item found in list. Items inlist must be separated by white space (space, tab, or newline characters). The list may be generated bymany different methods including: file expansion, variable substitution, positional parameters, commandsubstitution, or by simply providing a list of strings immediately following the in statement.

$ for file in ./* > do > file ${file} > done ./bin: directory ./ftp: directory ./majordomo.txt: English text ./mig29.ps: PostScript document ./ph.dat: ascii text ./prologues: directory ./source: directory

Here, the for loop takes advantage of filename expansion. The ./* directs the loop to process all of the files located in the current directory. The intermediate variable used is named file. Its values are passed tothe filecommand. This command gives a best guess description of what type of file the argument is. Todifferentiate the file command from the intermediate variable, the variable of course is surrounded by curlybraces. As for the results, bin, ftp, prologues, and source are all directories; majordomo.txt isconsidered to be prose; mig29.ps is a PostScript document; and ph.dat is a plain ASCII file.

The same results can be achieved using command substitution. Surely, by using ls in the working directory,the loop processes the same arguments:

$ for file in `ls` > do > file ${file} > done ./bin: directory ./ftp: directory ./majordomo.txt: English text ./mig29.ps: PostScript document ./ph.dat: ascii text

Bourne_Shell_Programming-Robert_P_Sayle.html file:///Z:/VMWare_Shared/Bourne_Shell_Programming-Robert_P_Say...

52 de 102 11/10/2010 16:13

./prologues: directory

./source: directory

In fact, the commands passed to a forloop as a list can be as simple or complex as a scripter wishes; however, for readability and for ease ofdebugging, a programmer should consider assigning the command's output to a variable and then passing thevariable's value as the loop's argument list.

$ NOPASSWD=`ypcat passwd | grep "^[a-zA-Z0-9]*::" | cut -f1 -d:` $ for user in ${NOPASSWD} > do > mail ${user} << END > Please set your password. You currently don't have one! > END > done

Here's a nice hackers' example, or perhaps a tool for a security conscious system administrator. The scriptbegins by assigning to NOPASSWDthe results of a rather lengthy command filter. The command passes the NIS password database to grep. Grep

searches for any entry without a password. The passwd database consists of a set of fields delimited bycolons. The second field is the account's encrypted password; hence, an account without a password wouldbe denoted by an entry with a double colon immediately following the account name. The list grepgenerates is then passed to cutto parse out the offending account names from the database entry. After this building the list, thesubsequent for loop sends an email to each user warning them that they must correct the security problemthey created.

For the last consideration of for loops, the in list portion is optional. If omitted, the loop uses thepositional parameters as its arguments by default. This is equivalent to writing, "for var in "$@" do ..." This technique should be used with care because it is not always immediately obvious to anotherreading the script what the arguments of the loop are.

$ cat printargs #!/bin/sh for arg do echo ${arg} done $ printargs foo bar * foo bar bin printargs

This simple example quickly illustrates the concept. The user executes the printargs script which echos the arguments back to the console. The arguments are the strings foo and bar as well as the contents of thecirrent directory as given by the shell's file expansion of *.

6.2 While Loops

A while loop is different from a forloop in that is uses a test condition to determine whether or not to execute its command block. As long as thecondition returns true when tested, the loop executes the commands. If it returns false, the script skips theloop and proceeds beyond its termination point. Consequently, the command set could conceiveably neverrun. This is further illustrated by the loop's syntax:

Bourne_Shell_Programming-Robert_P_Sayle.html file:///Z:/VMWare_Shared/Bourne_Shell_Programming-Robert_P_Say...

53 de 102 11/10/2010 16:13

while condition do command1 : commandn done

Because the loop starts with the test, if the condition fails, then the program continues at whatever actionimmediately follows the loop's donestatement. If the condition passes, then the script executes the enclosed command block. After performingthe last command in the block, the loop tests the condition again and determines whether to proceed beyondthe loop or fall through it once more.

$ i=1 $ while [ ${i} -le 5 ] > do > echo ${i} > i=`expr ${i} + 1` > done 1 2 3 4 5

The preceeding example demonstrates a while loop. Given the variable i whose initial value is one, the loop starts by checking if i is less than or equal to five. Since it is, the loop prints i's value, increments it by one as shown with the expr command, and then tests its value once more. This continues until i reachessix. At that point, the loop terminates, and the shell returns control to the user.

The built-in command shift works well with while loops. Shift causes the positional parameters' values toleft-shift by its argument. Shiftcan take any non-negative value as its argument and reset the positional parameters accordingly. By default,shift

moves the arguments by one. When the parameters are shifted by one, the first is dropped from the list, thesecond parameter's value becomes that of the first, and third's becomes the second, and so on up to the ninthparameter. This technique can be used effectively with a while loop for processing script options orfunction arguments.

$ cat printargs #!/bin/sh while [ $# -gt 0 ] do echo "$@" shift done $ printargs hello world "foo bar" bye hello world foo bar bye world foo bar bye foo bar bye bye

The printargs script combines a while loop with the shift command. The script takes an argument listand prints the entire list. After doing so, it then shifts the arguments by one and repeats the process until itshifts the arguments out of existence. The example demonstrates this with a four argument list of words. The third argument is two words passed as one variable by enclosing them in double-quotes.

Bourne_Shell_Programming-Robert_P_Sayle.html file:///Z:/VMWare_Shared/Bourne_Shell_Programming-Robert_P_Say...

54 de 102 11/10/2010 16:13

6.3 Until Loops

Until loops are the other while loops. They work similarly in nature to while loops except for one majordifference. An until loop executes its command block only if the test condition is false. In this sense, anuntil loop can be viewed as the logical negation of a while loop.

until condition do command1 : commandn done

Once again, the command block may never be executed as shown in its syntax. If the condition evaluates totrue, then the script proceeds beyond the loop.

To demonstrate, the example below shows how to rewrite the printargs script above changing the while to an until loop:

$ cat printargs2 #!/bin/sh until [ $# -le 0 ] do echo "$@" shift done $ printargs2 hello world "foo bar" bye hello world foo bar bye world foo bar bye foo bar bye bye

6.4 Loop Control

Sometimes it is necessary to end a loop before the test condition is satisfied or a list is completelyprocessed. In such cases, the scripter uses the break command. When encountered, the break command instructs the shell to continue processing beyond the enclosing loop. In other words, script execution picksup just after the done statement.

$ ls -l total 4 -rw-r--r-- 1 rsayle users 128 Aug 16 23:00 catfiles -rw-r--r-- 1 rsayle users 48 Aug 16 23:01 exrc drwxr-xr-x 2 rsayle users 1024 Aug 16 23:02 include/ -rw-r--r-- 1 rsayle users 89 Aug 16 23:02 profile $ cat catfiles #!/bin/sh for file in ./* do if [ ! -d ${file} ]; then echo "${file}:" cat ${file} echo else break

Bourne_Shell_Programming-Robert_P_Sayle.html file:///Z:/VMWare_Shared/Bourne_Shell_Programming-Robert_P_Say...

55 de 102 11/10/2010 16:13

fi done echo echo "End of catfiles"

For this example, the current working directory contains four objects: the script catfiles, an exrc file containing the user's vi defaults, an include directory holding a users header files, and a profilecontaining the user's shell default environment. Looking at catfiles, the script runs a for loop against allfiles in the working directory. If the file is not a directory, the script prints a line with the file's namefollowed by a colon. Then the program cats the file printing its contents to the screen. If the file is adirectory, however, the script breaks the for loop and prints a termination message before exiting. Runningcatfiles against the directory produces:

$ catfiles ./catfiles: #!/bin/sh for file in ./* do if [ ! -d ${file} ]; then echo "${file}:" cat ${file} else break fi done

./exrc: set number autoindent tabstop=2 showmode nowrap

End of catfiles $

The files, as listed by the previous lscommand, are processed in order. As the script encounters each file before the include directory, it cats the file. Once it hits include, the script completes leaving the contents of profile a mystery.

Had the programmer intended to view all files except directories, the programmer would have used thecontinue command instead. When the shell encounters a continue it skips the current processing step andproceeds with the loop's next iteration. The loop does not just quit as it does with break. It just executes thenext trial until the test condition is satisfied or the argument list is exhausted.

Taking a look at catfiles using a continue instead of a break shows the difference. This time the scriptsteps past the include directory and dumps the contents of profile.

$ catfiles ./catfiles: #!/bin/sh for file in ./* do if [ ! -d ${file} ]; then echo "${file}:" cat ${file} else continue fi done

./exrc: set number autoindent tabstop=2 showmode nowrap

Bourne_Shell_Programming-Robert_P_Sayle.html file:///Z:/VMWare_Shared/Bourne_Shell_Programming-Robert_P_Say...

56 de 102 11/10/2010 16:13

./profile: # setup terminal characteristics export TERM=vt100 set -o vi # turn off mess

6. LoopingFor Loops1.While Loops2.Until Loops3.Loop Control4.

6.1 For Loops

With functions, variables, branching, and soon looping under the proverbial belt, all the tools to createuseful shell scripts are at hand. But first, one must understand how to build and control shell loops. A looprepeats a command block based upon a set of criteria that determines when to continue or terminate theloop. Loops are a hallmark of all programming languages because they allow computers to do with greatprecision and speed what humans are not so terribly great at: repeating actions.

In the Bourne shell, the first type to consider is the for loop. A for loop processes multiple commands on alist of items. This is a bit non-traditional in that languages such as C and FORTRAN employ for loops as asort of counting mechanism; the loop continues until the variable reaches a target value. But here, the forloop uses the intermediate variable to execute actions on a set of items such as files or the results of acommand. In this way, the shell's for loop is a more flexible and useful than in most other languages.

The syntax of a for loop is:

for variable in list do command1 : commandn done

The for loop's variablemay be any legal shell variable. It need not be declared prior to the loop, and in fact, good shellprogramming practice mandates that the scripter should choose a unique variable that is not and will not beused elsewhere. This is punctuated by the fact that the variable does become global in the script. As the forloop executes the command block, the value of variable changes into the next item found in list. Items inlist must be separated by white space (space, tab, or newline characters). The list may be generated bymany different methods including: file expansion, variable substitution, positional parameters, commandsubstitution, or by simply providing a list of strings immediately following the in statement.

$ for file in ./* > do > file ${file} > done ./bin: directory ./ftp: directory ./majordomo.txt: English text ./mig29.ps: PostScript document ./ph.dat: ascii text

Bourne_Shell_Programming-Robert_P_Sayle.html file:///Z:/VMWare_Shared/Bourne_Shell_Programming-Robert_P_Say...

57 de 102 11/10/2010 16:13

./prologues: directory

./source: directory

Here, the for loop takes advantage of filename expansion. The ./* directs the loop to process all of the files located in the current directory. The intermediate variable used is named file. Its values are passed tothe filecommand. This command gives a best guess description of what type of file the argument is. Todifferentiate the file command from the intermediate variable, the variable of course is surrounded by curlybraces. As for the results, bin, ftp, prologues, and source are all directories; majordomo.txt isconsidered to be prose; mig29.ps is a PostScript document; and ph.dat is a plain ASCII file.

The same results can be achieved using command substitution. Surely, by using ls in the working directory,the loop processes the same arguments:

$ for file in `ls` > do > file ${file} > done ./bin: directory ./ftp: directory ./majordomo.txt: English text ./mig29.ps: PostScript document ./ph.dat: ascii text ./prologues: directory ./source: directory

In fact, the commands passed to a forloop as a list can be as simple or complex as a scripter wishes; however, for readability and for ease ofdebugging, a programmer should consider assigning the command's output to a variable and then passing thevariable's value as the loop's argument list.

$ NOPASSWD=`ypcat passwd | grep "^[a-zA-Z0-9]*::" | cut -f1 -d:` $ for user in ${NOPASSWD} > do > mail ${user} << END > Please set your password. You currently don't have one! > END > done

Here's a nice hackers' example, or perhaps a tool for a security conscious system administrator. The scriptbegins by assigning to NOPASSWDthe results of a rather lengthy command filter. The command passes the NIS password database to grep. Grep

searches for any entry without a password. The passwd database consists of a set of fields delimited bycolons. The second field is the account's encrypted password; hence, an account without a password wouldbe denoted by an entry with a double colon immediately following the account name. The list grepgenerates is then passed to cutto parse out the offending account names from the database entry. After this building the list, thesubsequent for loop sends an email to each user warning them that they must correct the security problemthey created.

For the last consideration of for loops, the in list portion is optional. If omitted, the loop uses thepositional parameters as its arguments by default. This is equivalent to writing, "for var in "$@" do ..." This technique should be used with care because it is not always immediately obvious to anotherreading the script what the arguments of the loop are.

$ cat printargs #!/bin/sh

Bourne_Shell_Programming-Robert_P_Sayle.html file:///Z:/VMWare_Shared/Bourne_Shell_Programming-Robert_P_Say...

58 de 102 11/10/2010 16:13

for arg do echo ${arg} done $ printargs foo bar * foo bar bin printargs

This simple example quickly illustrates the concept. The user executes the printargs script which echos the arguments back to the console. The arguments are the strings foo and bar as well as the contents of thecirrent directory as given by the shell's file expansion of *.

6.2 While Loops

A while loop is different from a forloop in that is uses a test condition to determine whether or not to execute its command block. As long as thecondition returns true when tested, the loop executes the commands. If it returns false, the script skips theloop and proceeds beyond its termination point. Consequently, the command set could conceiveably neverrun. This is further illustrated by the loop's syntax:

while condition do command1 : commandn done

Because the loop starts with the test, if the condition fails, then the program continues at whatever actionimmediately follows the loop's donestatement. If the condition passes, then the script executes the enclosed command block. After performingthe last command in the block, the loop tests the condition again and determines whether to proceed beyondthe loop or fall through it once more.

$ i=1 $ while [ ${i} -le 5 ] > do > echo ${i} > i=`expr ${i} + 1` > done 1 2 3 4 5

The preceeding example demonstrates a while loop. Given the variable i whose initial value is one, the loop starts by checking if i is less than or equal to five. Since it is, the loop prints i's value, increments it by one as shown with the expr command, and then tests its value once more. This continues until i reachessix. At that point, the loop terminates, and the shell returns control to the user.

The built-in command shift works well with while loops. Shift causes the positional parameters' values toleft-shift by its argument. Shiftcan take any non-negative value as its argument and reset the positional parameters accordingly. By default,shift

Bourne_Shell_Programming-Robert_P_Sayle.html file:///Z:/VMWare_Shared/Bourne_Shell_Programming-Robert_P_Say...

59 de 102 11/10/2010 16:13

moves the arguments by one. When the parameters are shifted by one, the first is dropped from the list, thesecond parameter's value becomes that of the first, and third's becomes the second, and so on up to the ninthparameter. This technique can be used effectively with a while loop for processing script options orfunction arguments.

$ cat printargs #!/bin/sh while [ $# -gt 0 ] do echo "$@" shift done $ printargs hello world "foo bar" bye hello world foo bar bye world foo bar bye foo bar bye bye

The printargs script combines a while loop with the shift command. The script takes an argument listand prints the entire list. After doing so, it then shifts the arguments by one and repeats the process until itshifts the arguments out of existence. The example demonstrates this with a four argument list of words. The third argument is two words passed as one variable by enclosing them in double-quotes.

6.3 Until Loops

Until loops are the other while loops. They work similarly in nature to while loops except for one majordifference. An until loop executes its command block only if the test condition is false. In this sense, anuntil loop can be viewed as the logical negation of a while loop.

until condition do command1 : commandn done

Once again, the command block may never be executed as shown in its syntax. If the condition evaluates totrue, then the script proceeds beyond the loop.

To demonstrate, the example below shows how to rewrite the printargs script above changing the while to an until loop:

$ cat printargs2 #!/bin/sh until [ $# -le 0 ] do echo "$@" shift done $ printargs2 hello world "foo bar" bye hello world foo bar bye world foo bar bye foo bar bye bye

Bourne_Shell_Programming-Robert_P_Sayle.html file:///Z:/VMWare_Shared/Bourne_Shell_Programming-Robert_P_Say...

60 de 102 11/10/2010 16:13

6.4 Loop Control

Sometimes it is necessary to end a loop before the test condition is satisfied or a list is completelyprocessed. In such cases, the scripter uses the break command. When encountered, the break command instructs the shell to continue processing beyond the enclosing loop. In other words, script execution picksup just after the done statement.

$ ls -l total 4 -rw-r--r-- 1 rsayle users 128 Aug 16 23:00 catfiles -rw-r--r-- 1 rsayle users 48 Aug 16 23:01 exrc drwxr-xr-x 2 rsayle users 1024 Aug 16 23:02 include/ -rw-r--r-- 1 rsayle users 89 Aug 16 23:02 profile $ cat catfiles #!/bin/sh for file in ./* do if [ ! -d ${file} ]; then echo "${file}:" cat ${file} echo else break fi done echo echo "End of catfiles"

For this example, the current working directory contains four objects: the script catfiles, an exrc file containing the user's vi defaults, an include directory holding a users header files, and a profilecontaining the user's shell default environment. Looking at catfiles, the script runs a for loop against allfiles in the working directory. If the file is not a directory, the script prints a line with the file's namefollowed by a colon. Then the program cats the file printing its contents to the screen. If the file is adirectory, however, the script breaks the for loop and prints a termination message before exiting. Runningcatfiles against the directory produces:

$ catfiles ./catfiles: #!/bin/sh for file in ./* do if [ ! -d ${file} ]; then echo "${file}:" cat ${file} else break fi done

./exrc: set number autoindent tabstop=2 showmode nowrap

End of catfiles $

The files, as listed by the previous lscommand, are processed in order. As the script encounters each file before the include directory, it cats

Bourne_Shell_Programming-Robert_P_Sayle.html file:///Z:/VMWare_Shared/Bourne_Shell_Programming-Robert_P_Say...

61 de 102 11/10/2010 16:13

the file. Once it hits include, the script completes leaving the contents of profile a mystery.

Had the programmer intended to view all files except directories, the programmer would have used thecontinue command instead. When the shell encounters a continue it skips the current processing step andproceeds with the loop's next iteration. The loop does not just quit as it does with break. It just executes thenext trial until the test condition is satisfied or the argument list is exhausted.

Taking a look at catfiles using a continue instead of a break shows the difference. This time the scriptsteps past the include directory and dumps the contents of profile.

$ catfiles ./catfiles: #!/bin/sh for file in ./* do if [ ! -d ${file} ]; then echo "${file}:" cat ${file} else continue fi done

./exrc: set number autoindent tabstop=2 showmode nowrap

./profile: # setup terminal characteristics export TERM=vt100 set -o vi # turn off mess

6. LoopingFor Loops1.While Loops2.Until Loops3.Loop Control4.

6.1 For Loops

With functions, variables, branching, and soon looping under the proverbial belt, all the tools to createuseful shell scripts are at hand. But first, one must understand how to build and control shell loops. A looprepeats a command block based upon a set of criteria that determines when to continue or terminate theloop. Loops are a hallmark of all programming languages because they allow computers to do with greatprecision and speed what humans are not so terribly great at: repeating actions.

In the Bourne shell, the first type to consider is the for loop. A for loop processes multiple commands on alist of items. This is a bit non-traditional in that languages such as C and FORTRAN employ for loops as asort of counting mechanism; the loop continues until the variable reaches a target value. But here, the forloop uses the intermediate variable to execute actions on a set of items such as files or the results of acommand. In this way, the shell's for loop is a more flexible and useful than in most other languages.

The syntax of a for loop is:

Bourne_Shell_Programming-Robert_P_Sayle.html file:///Z:/VMWare_Shared/Bourne_Shell_Programming-Robert_P_Say...

62 de 102 11/10/2010 16:13

for variable in list do command1 : commandn done

The for loop's variablemay be any legal shell variable. It need not be declared prior to the loop, and in fact, good shellprogramming practice mandates that the scripter should choose a unique variable that is not and will not beused elsewhere. This is punctuated by the fact that the variable does become global in the script. As the forloop executes the command block, the value of variable changes into the next item found in list. Items inlist must be separated by white space (space, tab, or newline characters). The list may be generated bymany different methods including: file expansion, variable substitution, positional parameters, commandsubstitution, or by simply providing a list of strings immediately following the in statement.

$ for file in ./* > do > file ${file} > done ./bin: directory ./ftp: directory ./majordomo.txt: English text ./mig29.ps: PostScript document ./ph.dat: ascii text ./prologues: directory ./source: directory

Here, the for loop takes advantage of filename expansion. The ./* directs the loop to process all of the files located in the current directory. The intermediate variable used is named file. Its values are passed tothe filecommand. This command gives a best guess description of what type of file the argument is. Todifferentiate the file command from the intermediate variable, the variable of course is surrounded by curlybraces. As for the results, bin, ftp, prologues, and source are all directories; majordomo.txt isconsidered to be prose; mig29.ps is a PostScript document; and ph.dat is a plain ASCII file.

The same results can be achieved using command substitution. Surely, by using ls in the working directory,the loop processes the same arguments:

$ for file in `ls` > do > file ${file} > done ./bin: directory ./ftp: directory ./majordomo.txt: English text ./mig29.ps: PostScript document ./ph.dat: ascii text ./prologues: directory ./source: directory

In fact, the commands passed to a forloop as a list can be as simple or complex as a scripter wishes; however, for readability and for ease ofdebugging, a programmer should consider assigning the command's output to a variable and then passing thevariable's value as the loop's argument list.

$ NOPASSWD=`ypcat passwd | grep "^[a-zA-Z0-9]*::" | cut -f1 -d:` $ for user in ${NOPASSWD}

Bourne_Shell_Programming-Robert_P_Sayle.html file:///Z:/VMWare_Shared/Bourne_Shell_Programming-Robert_P_Say...

63 de 102 11/10/2010 16:13

> do > mail ${user} << END > Please set your password. You currently don't have one! > END > done

Here's a nice hackers' example, or perhaps a tool for a security conscious system administrator. The scriptbegins by assigning to NOPASSWDthe results of a rather lengthy command filter. The command passes the NIS password database to grep. Grep

searches for any entry without a password. The passwd database consists of a set of fields delimited bycolons. The second field is the account's encrypted password; hence, an account without a password wouldbe denoted by an entry with a double colon immediately following the account name. The list grepgenerates is then passed to cutto parse out the offending account names from the database entry. After this building the list, thesubsequent for loop sends an email to each user warning them that they must correct the security problemthey created.

For the last consideration of for loops, the in list portion is optional. If omitted, the loop uses thepositional parameters as its arguments by default. This is equivalent to writing, "for var in "$@" do ..." This technique should be used with care because it is not always immediately obvious to anotherreading the script what the arguments of the loop are.

$ cat printargs #!/bin/sh for arg do echo ${arg} done $ printargs foo bar * foo bar bin printargs

This simple example quickly illustrates the concept. The user executes the printargs script which echos the arguments back to the console. The arguments are the strings foo and bar as well as the contents of thecirrent directory as given by the shell's file expansion of *.

6.2 While Loops

A while loop is different from a forloop in that is uses a test condition to determine whether or not to execute its command block. As long as thecondition returns true when tested, the loop executes the commands. If it returns false, the script skips theloop and proceeds beyond its termination point. Consequently, the command set could conceiveably neverrun. This is further illustrated by the loop's syntax:

while condition do command1 : commandn done

Because the loop starts with the test, if the condition fails, then the program continues at whatever actionimmediately follows the loop's done

Bourne_Shell_Programming-Robert_P_Sayle.html file:///Z:/VMWare_Shared/Bourne_Shell_Programming-Robert_P_Say...

64 de 102 11/10/2010 16:13

statement. If the condition passes, then the script executes the enclosed command block. After performingthe last command in the block, the loop tests the condition again and determines whether to proceed beyondthe loop or fall through it once more.

$ i=1 $ while [ ${i} -le 5 ] > do > echo ${i} > i=`expr ${i} + 1` > done 1 2 3 4 5

The preceeding example demonstrates a while loop. Given the variable i whose initial value is one, the loop starts by checking if i is less than or equal to five. Since it is, the loop prints i's value, increments it by one as shown with the expr command, and then tests its value once more. This continues until i reachessix. At that point, the loop terminates, and the shell returns control to the user.

The built-in command shift works well with while loops. Shift causes the positional parameters' values toleft-shift by its argument. Shiftcan take any non-negative value as its argument and reset the positional parameters accordingly. By default,shift

moves the arguments by one. When the parameters are shifted by one, the first is dropped from the list, thesecond parameter's value becomes that of the first, and third's becomes the second, and so on up to the ninthparameter. This technique can be used effectively with a while loop for processing script options orfunction arguments.

$ cat printargs #!/bin/sh while [ $# -gt 0 ] do echo "$@" shift done $ printargs hello world "foo bar" bye hello world foo bar bye world foo bar bye foo bar bye bye

The printargs script combines a while loop with the shift command. The script takes an argument listand prints the entire list. After doing so, it then shifts the arguments by one and repeats the process until itshifts the arguments out of existence. The example demonstrates this with a four argument list of words. The third argument is two words passed as one variable by enclosing them in double-quotes.

6.3 Until Loops

Until loops are the other while loops. They work similarly in nature to while loops except for one majordifference. An until loop executes its command block only if the test condition is false. In this sense, anuntil loop can be viewed as the logical negation of a while loop.

until condition do

Bourne_Shell_Programming-Robert_P_Sayle.html file:///Z:/VMWare_Shared/Bourne_Shell_Programming-Robert_P_Say...

65 de 102 11/10/2010 16:13

command1 : commandn done

Once again, the command block may never be executed as shown in its syntax. If the condition evaluates totrue, then the script proceeds beyond the loop.

To demonstrate, the example below shows how to rewrite the printargs script above changing the while to an until loop:

$ cat printargs2 #!/bin/sh until [ $# -le 0 ] do echo "$@" shift done $ printargs2 hello world "foo bar" bye hello world foo bar bye world foo bar bye foo bar bye bye

6.4 Loop Control

Sometimes it is necessary to end a loop before the test condition is satisfied or a list is completelyprocessed. In such cases, the scripter uses the break command. When encountered, the break command instructs the shell to continue processing beyond the enclosing loop. In other words, script execution picksup just after the done statement.

$ ls -l total 4 -rw-r--r-- 1 rsayle users 128 Aug 16 23:00 catfiles -rw-r--r-- 1 rsayle users 48 Aug 16 23:01 exrc drwxr-xr-x 2 rsayle users 1024 Aug 16 23:02 include/ -rw-r--r-- 1 rsayle users 89 Aug 16 23:02 profile $ cat catfiles #!/bin/sh for file in ./* do if [ ! -d ${file} ]; then echo "${file}:" cat ${file} echo else break fi done echo echo "End of catfiles"

For this example, the current working directory contains four objects: the script catfiles, an exrc file containing the user's vi defaults, an include directory holding a users header files, and a profilecontaining the user's shell default environment. Looking at catfiles, the script runs a for loop against allfiles in the working directory. If the file is not a directory, the script prints a line with the file's name

Bourne_Shell_Programming-Robert_P_Sayle.html file:///Z:/VMWare_Shared/Bourne_Shell_Programming-Robert_P_Say...

66 de 102 11/10/2010 16:13

followed by a colon. Then the program cats the file printing its contents to the screen. If the file is adirectory, however, the script breaks the for loop and prints a termination message before exiting. Runningcatfiles against the directory produces:

$ catfiles ./catfiles: #!/bin/sh for file in ./* do if [ ! -d ${file} ]; then echo "${file}:" cat ${file} else break fi done

./exrc: set number autoindent tabstop=2 showmode nowrap

End of catfiles $

The files, as listed by the previous lscommand, are processed in order. As the script encounters each file before the include directory, it cats the file. Once it hits include, the script completes leaving the contents of profile a mystery.

Had the programmer intended to view all files except directories, the programmer would have used thecontinue command instead. When the shell encounters a continue it skips the current processing step andproceeds with the loop's next iteration. The loop does not just quit as it does with break. It just executes thenext trial until the test condition is satisfied or the argument list is exhausted.

Taking a look at catfiles using a continue instead of a break shows the difference. This time the scriptsteps past the include directory and dumps the contents of profile.

$ catfiles ./catfiles: #!/bin/sh for file in ./* do if [ ! -d ${file} ]; then echo "${file}:" cat ${file} else continue fi done

./exrc: set number autoindent tabstop=2 showmode nowrap

./profile: # setup terminal characteristics export TERM=vt100 set -o vi # turn off messaging mesg n

Bourne_Shell_Programming-Robert_P_Sayle.html file:///Z:/VMWare_Shared/Bourne_Shell_Programming-Robert_P_Say...

67 de 102 11/10/2010 16:13

7. Handling Command Line OptionsUNIX Command Line Option Styles1.Getopts Statements2.

7.1 UNIX Command Line Option Styles

Options are switches to commands. They instruct the program to perform specific processing. Optionsappear on the command line following the command itself. They are recognizable by a preceding dash andcome in two forms, separated and stacked:

separated: rdist -b -h -q -c /etc ren:/etc1.stacked: ls -alg2.

It should be noted that not all programs require their options to be preceded by a dash character. In suchcases, the options are stacked and immediately follow the command. The ps and tar programs are suchexceptions: ps waux and tar cvf /home/user /tmp/backup.

Some options take mandatory arguments. When the program reads the option, it searches for the associatedargument in order to process it accordingly. Usually, these options are listed separately with its argument asthe next command line entry. An example is the make command used to compile a C program: make all -fbuild.mk -targetdir ./proj/bin. Here, the command line instructs make to build all parts of theprogram. It then tells make to take its build rules from the file build.mk as shown by the -f option. It alsotells make to place the results in the directory ./proj/bin as given by the -targetdir option. The lastswitch shows that options need not just be single letters. Somtimes, programs interpret whole words asoptions.

Not all commands require the separation of options and arguments. For some programs it may be perfectlyfine to stack the options and then list their arguments respectively. Tar is a perfect example of this: tar cvbf 120 /home/user /tmp/backup. In the example, tar's first two arguments cause the program to createa new tape archive and use verbose mode so that the user can see what it is doing. Then the options witharguments are listed. The b option specifies the blocking factor to use, and the f option specifies the file, ordirectory in this case, to tar. The options' arguments then follow in order. Tar uses a blocking factor of 120bytes per block and stores the contents of the user's home directory. Finally, the last argument to tar is the archive that it creates, /tmp/backup.

Careful examination of a program's man page shows its legal option and argument list.

7.2 Getopts Statements

Considering the programming facilities presented thus far, it is plain to see that options are easy to handleusing the positional parameters, a loop, some shifts, and a case statement:

#!/bin/sh # # setether: set an Ethernet interface's IP configuration #

while [ $# -gt 1 ]

Bourne_Shell_Programming-Robert_P_Sayle.html file:///Z:/VMWare_Shared/Bourne_Shell_Programming-Robert_P_Say...

68 de 102 11/10/2010 16:13

do case ${1} a) ARP="arp" shift ;; b) BROADCAST=${2} shift 2 ;; i) IPADDRESS=${2} shift 2 ;; m) NETMASK=${2} shift 2 ;; n) NETWORK=${2} shift 2 ;; *) echo "setether: illegal option: ${1}" exit 1 ;; esac done

INTERFACE=${1}

ifconfig ${INTERFACE} ${IPADDRESS} netmask ${NETMASK} broadcast ${BROADCAST} ${ARP} route add -net ${NETWORK}

The setetherscript processes a number of options and arguments in order to properly configure an Ethernet interface foruse on an IP network. It uses a while loop and a case statement to do this. The scripts switches on theoptions using the caseblock. The options are the one letter switches and are expected by the script to be in the first position on thecommand line. Each option's argument is expected to follow the option in the second position. Assetether processes the options, it shifts the positional parameters by two in order to correctly handle thenext one. The only exception to this is the -aoption which toggles on ARP for the interface. Because it has no argument, the option requires the script toshift

by one parameter only. After reading all the options and their arguments, the script takes the last argument,which it expects to be the interface to be configured. Lastly, the script does its real work; namely, it sets theinterface using the ifconfig command, and then it adds the network route.

Processing options in this manner works but is rigid. The programmer must pay careful attention to theshift

pattern in order to correctly handle options with arguments versus those without arguments. Anotherlimitation is the fact that it does not handle stacked options. Although stacked options are not an absolutenecessity, it is best to give the user the most flexibility with the command. The user can choose whichmethod of specifying the arguments is most comfortable. By assuming that the arguments follow theoptions, the user does not gain this advantage. Luckily, there is a better way to deal with options.

The shell provides a special utility specifically designed for processing command line options and theirarguments called getopts. Its syntax is: getopts options variable. Getopts takes two arguments. Thefirst is a list of valid command line options. If an option requires an argument, a scripter places a colondirectly after the option. The second is a temporary variable used for parsing through the arguments.

To use getopts, the programmer employs a loop. Each time getopts is called within the loop, it processesthe next command line argument. If it finds a valid option, then the option is stored in the variable. If itencounters an invalid option, getopts stores a ?

Bourne_Shell_Programming-Robert_P_Sayle.html file:///Z:/VMWare_Shared/Bourne_Shell_Programming-Robert_P_Say...

69 de 102 11/10/2010 16:13

inside variable for error handling. When an option takes an argument, getopts stores the associatedargument in the special variable OPTARG. If it cannot locate the option's argument, getopts sets OPTARG to ?.

From a user's point of view, a script using getopts follows one simple rule. Options must always bepreceded by a dash. If listed separately, each option takes a dash. If stacked, the option list begins with adash. This is required for getoptsto differentiate between options and their arguments. It is hardly a burden for users to do this and will helpbeginners of UNIX to learn its syntax.

Returning to the previous example, setether can be rewritten with getopts:

#!/bin/sh # # setether: set an Ethernet interface's IP configuration #

while getopts ab:e:i:m:n: option do case "${option}" in a) ARP="arp" b) BROADCAST=${OPTARG};; e) INTERFACE=${OPTARG};; i) IPADDRESS=${OPTARG};; m) NETMASK=${OPTARG};; n) NETWORK=${OPTARG};; *) echo "setether: illegal option: ${option}" exit 1 ;; esac done

ifconfig ${INTERFACE} ${IPADDRESS} netmask ${NETMASK} broadcast ${BROADCAST} ${ARP} route add -net ${NETWORK}

Now setether loops upon the results of getopts. The script lists the valid command line options in thestring ab:i:m:n:. The colons trailing the last four arguments force getopts to find the options' arguments. Then when the case statement tests for the current option processed, the script reads the argument fromOPTARG. Upon completion of processing the arguments, the program sets the interface's IP configuration.

The new version improves upon the original in a number of ways. For one, it assumes no special order of thecommand line arguments. The script even paraterizes the target interface so that it can be specified in anyposition. The real advantage to all of this is that the program lets getopts do the work. The programmerneed not be concerned with any special shifting patterns necessary to get to the next argument. Anotheradvantage is that the options can be stacked. If the user so desires, the options can be listed together ratherthan separately with their arguments. Also if an option is missing its argument, getopts performs errorchecking to make sure that one is present. There is now some built-in exception handling that was notpresent before. In the origial version, it is quite possible that the user could have forgotten an argument. Then the script would have happily read the next option as the previous option's argument. This would havedeleterious results on the script's operation because the shift pattern would be incorrect. Finally, the newversion is compact. It is smaller in code size and much easier to read since it focuses on the task at hand,namely, the processing of command line arguments.

The only disadvantage to getoptsis the fact that it can only handle single character options. It will not properly process options that are wordsas some programs do. If a script has so many options that it runs out of characters to handle them, then thescripter should consider processing positional parameters. On the other hand, the scripter should alsoconsider that the program being written could possibly use a redesign in order to make it easier to use.

Bourne_Shell_Programming-Robert_P_Sayle.html file:///Z:/VMWare_Shared/Bourne_Shell_Programming-Robert_P_Say...

70 de 102 11/10/2010 16:13

8. Advanced Shell TopicsMore I/O1.Synchronicity2.Arithmetic3.Eval Statements4.Exec Statements5.Setting Shell Flags6.Signals and Traps7.

8.1 More I/O

Input and output techniques within the shell was first introduced in Section 1.4, Shell Basics: I/O. Thatsection covered input and output redirection of stdin and stdout to files. By now, the reader should be fairlyfamiliar with these techniques from having used them to write to and read from text files. This sectionexpands upon these concepts by showing how to use the same constructs for redirection to file descriptorsand by combining loops with I/O redirection.

The shell permits the combination of the basic I/O constructs with a file descriptor. A file descriptor isnothing more than a non-negative digit that points at a file. File descriptors are also known as file handles. They are very useful for interprocess and network programming. The reader should not be terriblyconcerned if this concept is confusing. From the shell's perspective, only stdin, stdout, and stderr are trulyimportant. Anything else probably requires a more powerful language such as C, which can make systemcalls to create and gain access to other file descriptors.

The file descriptors for stdin, stdout, and stderr are 0, 1, and 2, respectively. Any of these may be redirectedto a file or to each other. In other words, it is quite possible to send the output from stdin and stderr to thesame file. This is quite useful when a user would rather check a script's results after it has completedprocessing. The methods for doing this are shown in Table 8.1-1.

Table 8.1-1. Methods for File Descriptor RedirectionConstruct Meaning

descriptor<file

ordescriptor<<file

Redirects descriptor's standard input to the same input that file uses.

descriptor>file

ordescriptor>>file

Redirects descriptor's standard output to the same output that file uses.

file<&descriptorRedirects descriptor's standard input to the same input that file uses. For use whenstandard input has already been redirected.

file>&descriptorRedirects descriptor's standard output to the same output that file uses. For usewhen standard output has already been redirected.

<&- Closes standard input>&- Closes standard output

Granted, this syntax looks strange, and in fact, it takes some practice to get them right. Below are someexamples.

$ cd /bogus >>out /bogus: does not exist

Bourne_Shell_Programming-Robert_P_Sayle.html file:///Z:/VMWare_Shared/Bourne_Shell_Programming-Robert_P_Say...

71 de 102 11/10/2010 16:13

$ cat out

The user starts by trying to change directories to a non-existant directory. In the process, the output fromthe command is redirected to the file out. Unfortunately, the user neglects that fact that the output generatedby cd is an error and is output on stderr. So when the user displays the contents of out, it contains nothing. This time, the user tries redirecting stderr to the same file in which stdout is redirected.

$ cd /bogus >>out 2>&1 $ cat out /bogus: does not exist

This time, out contains the error message. The key is the addition of 2>&1. This is a very importantconcept, and to understand it, the line should be read, "Change directory to /bogus, redirect stdout to the fileout, and then redirect stderr to the same file that stdout uses." The last part is exactly what 2>&1 means. Moreover, most scripters find that the 2>&1construct is the most widely if not the only of these constructs used. As stated previously, it is extremelyuseful for saving the output to a file for reviewing after the script completes. For example, a backup scriptthat runs each night might do this and then email the output to the system administrator.

As a quick note, the same results could have been generated simply by preceding the output redirection withstderr's file descriptor.

$ cd /bogus 2>>out $ cat out /bogus: does not exist

As the last example of advanced redirection, the results of printing the working directory are suppressed byclosing stdout.

$ pwd >&- $

Now using these within a script can become a bit tiresome. A programmer needs to precede each commandthat could produce output or errors with appropriate redirection. Actually, to get around this, the user justexecutes the script at the command line with the same redirection. But if the scripter chooses to do the workfor the user, then the shell provides a method for redirecting all of a script's input and output. A form of theexec command allows this: exec construct file, where construct is a redirection and file is the input oroutput to use. For example,

#!/bin/sh exec >>${0}.out

redirects the output for the script into the file given by ${0}.out. ${0}.out is the new file named the same as the script but with .out appended.

Next, redirection can be used with loops. To do so, the programmer adds the redirection construct to the endof the loop declaration. In fact, loops can use redirection, piping, and even backgrounding by the samemethod, though redirection is easily the most useful. Below is a short script demonstrating the technique byprinting the disk usage of each user on a system.

$ USERS=`cat /etc/passwd | cut -f1 -d:` $ for user in ${USERS} > do > du -sk /home/${user} > done >>diskusage 2>/dev/null $ cat diskusage 2748 /home/ftp 38931 /home/jac 109 /home/mlambi

Bourne_Shell_Programming-Robert_P_Sayle.html file:///Z:/VMWare_Shared/Bourne_Shell_Programming-Robert_P_Say...

72 de 102 11/10/2010 16:13

26151 /home/rsayle 5 /home/bobt 8185 /home/mann_al 13759 /home/bheintz

The script starts by getting a list of users from the /etc/passwd file. It stores the list in the variable USERS. Then it parses through the list with a forloop. Each iteration of the loop performs a disk usage summary on the current user's home directory. Theresults are in turn saved in the file diskusage by redirecting standard output into it. Standard error is sent tothe great bit bucket in the sky because some accounts listed in /etc/passwd do not actually have homedirectories. This would cause du to complain that /home/${user} for the current user does not exist. Theresults are then displayed by catting diskusage.

Standard input works well with loops. As it turns out, redirecting stdin with a loop is the way one can read afile line by line in order to process its contents. Combining this with a little output redirection creates a slickway to generate files from a template and a list of data to fill the template.

$ cat mkraddb #!/bin/sh # # mkraddb: generate the RADIUS configuration of a set of users # #

# Global Vars PROG=`basename ${0}` USAGE="${PROG}: usage: ${PROG} users_file" DATE=`date +%m%d%y` RADDBUSERS="raddb.users.${DATE}"

############################################## # buildRaddbUsers ##############################################

buildRaddbUsers () {

cat >>${RADDBUSERS} <<EOF ${name}-out Password = "ascend", User-Service = Dialout-Framed-User User-Name = ${name}, Framed-Protocol = MPP, Framed-Address = ${ipaddr}, Framed-Netmask = 255.255.255.255, Ascend-Metric = 2, Framed-Routing = None, Ascend-Route-IP = Route-IP-Yes, Ascend-Idle-Limit = 30, Ascend-Send-Auth = Send-Auth-CHAP, Ascend-Send-Passwd = "password", Ascend-Receive-Secret = "password"

EOF

}

############################################## # Main ##############################################

Bourne_Shell_Programming-Robert_P_Sayle.html file:///Z:/VMWare_Shared/Bourne_Shell_Programming-Robert_P_Say...

73 de 102 11/10/2010 16:13

if [ $# -ne 1 ]; then echo ${USAGE} exit 1 fi

# Create the configs while read name ipaddr do buildRaddbUsers done <${1}

# Clean up exit 0

So here is a script called mkraddb. Its sole purpose is to read a file and generate the RADIUS profiles forthe users listed in the file. Focusing on the redirection within the script, the program first uses redirectionwith the whileloop under the main program. The loop reads from the file passed to the script as its argument. Theredirection of the first positional parameter into the while loop does this. The read command following the while

serves as its test. It takes each line from the file and reads the line's contents into the temporary variablesname and ipaddr. As long as there are lines in the file to read, read returns true and the loop executes. When there are no more entires left, the loop terminates and, consequently, so does the script.

To process the data, the loop calls the function buildRaddbUsers. Turning back to this function shows thesecond usage of redirection within the script. Input redirection causes the function to create a new file asgiven by the value of RADDBUSERS. The output redirection of cat instructs the script to write into this file, the text between the end of the file markers, EOF. The shell expands the variables name and ipaddr as it writes the text to create the user's RADIUS profile. Here is how it works.

$ ls mkraddb users $ cat users bob 192.168.0.1 joe 172.16.20.2 mary 10.0.25.3

The user first gets a directory listing that shows only the script, mkraddb, and the users file. Then theoperator cats users and notes that the script should create RADIUS entries for bob, joe, and mary. Below,the operator executes mkraddb passing it users. A quick listing shows that mkraddb created raddb.users.082698, and inspecting the contents of the new file shows the profiles for these users.

$ mkraddb users $ ls mkraddb raddb.users.082698 users $ cat raddb.users.082698 bob-out Password = "ascend", User-Service = Dialout-Framed-User User-Name = bob, Framed-Protocol = MPP, Framed-Address = 192.168.0.1, Framed-Netmask = 255.255.255.255, Ascend-Metric = 2, Framed-Routing = None, Ascend-Route-IP = Route-IP-Yes, Ascend-Idle-Limit = 30, Ascend-Send-Auth = Send-Auth-CHAP, Ascend-Send-Passwd = "password",

Bourne_Shell_Programming-Robert_P_Sayle.html file:///Z:/VMWare_Shared/Bourne_Shell_Programming-Robert_P_Say...

74 de 102 11/10/2010 16:13

Ascend-Receive-Secret = "password"

joe-out Password = "ascend", User-Service = Dialout-Framed-User User-Name = joe, Framed-Protocol = MPP, Framed-Address = 172.16.20.2, Framed-Netmask = 255.255.255.255, Ascend-Metric = 2, Framed-Routing = None, Ascend-Route-IP = Route-IP-Yes, Ascend-Idle-Limit = 30, Ascend-Send-Auth = Send-Auth-CHAP, Ascend-Send-Passwd = "password", Ascend-Receive-Secret = "password"

mary-out Password = "ascend", User-Service = Dialout-Framed-User User-Name = mary, Framed-Protocol = MPP, Framed-Address = 10.0.25.3, Framed-Netmask = 255.255.255.255, Ascend-Metric = 2, Framed-Routing = None, Ascend-Route-IP = Route-IP-Yes, Ascend-Idle-Limit = 30, Ascend-Send-Auth = Send-Auth-CHAP, Ascend-Send-Passwd = "password", Ascend-Receive-Secret = "password"

8.2 Synchronicity

Due to UNIX's multitasking behavior, commands can be sent to the background so that they are executedwhen the kernel finds time. After backgrounding a command, the shell frees itself to handle othercommands. In other words, while the backgrounded command runs, the operator's shell is available toexecute other commands. To background a command, the user appends an ampersand, &, to it. Backgrounded commands are also referred to as jobs. Searching through a rather large hard disk for a file isa common candidate for backgrounding.

$ find / -name samunix.tar -print 2>/dev/null & [1] 10950 $ cd proj/book $ mkdir advanced $ cd advanced /home/rsayle/proj/ucb/samunix.tar $ touch example.sh [1]+ Exit 1 find / -name samunix.tar -print 2>/dev/null $

In this case, the operator decides to search for a missing tar file. The user runs the find command and instructs the program to start the search from the root directory. Any errors produced by find, namelydirectories that find is unable to search due to illegal priviliges for the user, are redirected to /dev/null. This way, the user can continue to do work without being interrupted. The user backgrounds the commandas well. The shell returns a message indicating that the command is running with process identifier 10950. If the user chooses, a kill signal can be sent to the command to end it prematurely. While the find runs, the operator continues with work. The operator changes the working directory, creates a new subdirectory, and

Bourne_Shell_Programming-Robert_P_Sayle.html file:///Z:/VMWare_Shared/Bourne_Shell_Programming-Robert_P_Say...

75 de 102 11/10/2010 16:13

then changes working directories once more into the newly created subdirectory. After this last action, thefind command returns a successful hit. It found the tar file in /home/rsayle/proj/ucb. The usercontinues by creating a new shell script called example.sh. The backgrounded job finishes its processingjust after this, and it returns a message stating that it is complete.

Sometimes it may be necessary to synchronize a script with an asynchronous process, ie. a backgroundedjob. The shell provides the wait built-in command for such synchronization. Wait takes an optionalprocess number as its argument. It pauses the shell's execution until the specified process had terminated. Ifonly one process has been sent to the background, then wait can be issued without an argument. It willautomatically wait for the job to complete. If more than one job is running, it becomes necessary to storetheir process identifiers in variables and pass the appropriate job number to wait.

#!/bin/sh # # stopOV: stop HP OpenView #

PROG=`basename ${0}`

# check for the root user if [ "${USER}" != "root" ]; then echo "${PROG}: only the root user can halt OpenView." exit 1 fi

# stop OpenView using its utility ovstop >>ovstop.$$ 2>&1 & echo "${PROG}: attempting to stop all OpenView processes..." wait

# check for runaway OpenView processes STILLRUNNING=`grep "RUNNING" ovstop.$$` if [ "${STILLRUNNING}" != "" ]; then for ovprocess in ${STILLRUNNING} do ovpid=`echo ${ovprocess} | cut -f1 -d" "` echo "${PROG}: killing OpenView process ${ovpid}" kill ${ovpid} & LASTOVPID=$! done fi

# wait for the last one to die wait ${LASTOVPID}

# notify administrator if there are still run away processes for ovproc in ${STILLRUNNING} do runaway=`ps -ef | grep ${ovproc}` if [ "${runaway}" != "" ]; then echo "${PROG}: could not stop OpenView process: ${runaway}" fi done

# Clean up exit 0

The script aboves uses both forms of the wait command. StopOV is a shell script that halts the HP OpenViewTM

Bourne_Shell_Programming-Robert_P_Sayle.html file:///Z:/VMWare_Shared/Bourne_Shell_Programming-Robert_P_Say...

76 de 102 11/10/2010 16:13

network management platform. When the root user runs it, the script first tries using the stop utility thatcomes with the software. It runs ovstop sending its output to the file ovstop.$$. If all goes well, then thisfile will be empty. The ovstop, as issued by the script also redirects stderr to the same file. If anyOpenView processes fail to terminate, it lists them in ovstop.$$ beginning with their process identifier. The script sends the command to the background, prints a friendly message indicating that it is working, andthen waits. Here the script uses wait without any arguments since ovstop is the only job.

Upon completion of ovstop, the script continues by checking ovstop.$$ for any errors. If it finds anyprocesses still running, as stored in the STILLRUNNING variable, then it tries to halt them with the killcommand. To do this, the script employs a for loop. It parses through the contents of STILLRUNNING andpicks out the errant processes. It sends a killsignal to these processes and backgrounds each attempt. Through each iteration, it stores the attempt inLASTOVPID. The final iteration of the loop resets LASTOVPID to the last kill issued. The script then waitsfor the last kill to complete. This of course assumes that all other kills issued complete prior to the lastone finishing.

This may be an errant assumption, but as it turns out, it is sufficient for this program because when the lastkill terminates, the scripts continues by checking the process table for any run away processes. StopOVshows this in the last forloop. If it finds any, it notifies the OpenView administrator so that the manager can take further action.

8.3 Arithmetic

Eventhough the shell treats all values as strings, a built-in command called expr interprets numeric stringsas their integral values. Moreover, it understands how to perform basic arithmetic including addition (+ and-) as well as multiplication (* and /). Actually, expr is much more feature rich than just a simplecalculator. Exprcan also handle pattern matching, substring evaluation, and logical relations (e.g. greater than comparisons). Still, for these functions, UNIX provides tools that are much better suited to these tasks such as grep, sed, and awk for pattern matching and the built-in logical operators for comparisons.

In any event, there are moments when a script calls for basic mathematics. A good example is when aprogrammer needs to increment a counter variable for a loop. The expr command suits such a task well.

As an example of how to use expr, the script below demonstrates the calculation of the PythagoreanTheorem.

$ cat csquared #!/bin/sh # # csquared: almost calculate the hypotenuse of a rt triangle #

PROG=`basename ${0}` USAGE="${PROG}: usage: ${PROG} base height"

if [ $# -ne 2 ]; then echo ${USAGE} exit 1 fi

ASQUARED=`expr ${1} \* ${1}` BSQUARED=`expr ${2} \* ${2}` expr ${ASQUARED} + ${BSQUARED}

$ csquared 3 4 25

Bourne_Shell_Programming-Robert_P_Sayle.html file:///Z:/VMWare_Shared/Bourne_Shell_Programming-Robert_P_Say...

77 de 102 11/10/2010 16:13

Because exprcannot handle complex math such as exponentials, the script breaks the calculation into three steps. Itevaluates the squares of each side of the right triangle and stores the results in the temporary variablesASQUARED and BSQUARED. During the evaluation of each expr, the programmer must escape themultiplication symbol so that the shell does not expand it as a wildcard. The final calculation, which is thesum of the squares, is handled directly by expr. It returns the result to the user's screen as demonstrated bythe invokation of csquared.

UNIX has a much more precise and powerful calculator called bc. It can evaluate real valued numbers,arrays, and exponentials, just to name a few. Bcutilizes an interactive interface that reads from stdin and writes to stdout, but it also supports reading fromand writing to files. This fact makes it useful for shell scripts.

Now csquared can be rewritten with bc making it a much more compat program.

$ cat csquared #!/bin/sh # # csquared: almost calculate the hypotenuse of a rt triangle #

PROG=`basename ${0}` USAGE="${PROG}: usage: ${PROG} base height"

if [ $# -ne 2 ]; then echo ${USAGE} exit 1 fi

bc <<EOF ${1}^2 + ${2}^2 quit EOF

$ csquared 3 4 25

This version of csquared does not need intermediate variables since bc can evaluate complex expressions. To pass the calculation to bc, the script employs input redirection, and to pass it the values for theexpression, the shell uses the positional parameters. Further, the calculation is handled in one line as thetheorem itself is expressed, a2 + b2. Bcprints the results on the user's terminal, but it could also easily be redirected to a file if needed.

8.4 Eval Statements

Eval

is a tricky beast. This built-in command instructs the shell to first evaluate the arguments and then toexecute the result. In other words, it performs any file expansion or variable substitution, then it executes thecommand. During the execution, the shell, because of its nature, once again does an expansion andsubstitution and then returns a result. Essentially, eval is a double scan of the command line. This is usefulfor command in which the shell does not process special constructs due to quoting.

$ spool=/var/spool $ ptr='$spool' $ echo; echo $ptr; echo

$spool

Bourne_Shell_Programming-Robert_P_Sayle.html file:///Z:/VMWare_Shared/Bourne_Shell_Programming-Robert_P_Say...

78 de 102 11/10/2010 16:13

$ eval echo $ptr /var/spool

The example above demonstrates the double scan by creating a pointer variable. First, the operator createsthe variable spool and stores the directory /var/spool into it. Next, the user declares a pointer, ptr, and stores in this variable $spool. The single quotes prevent the shell from evaluating $spool; hence, ptr does not contain spool's value. To show the value of spool, ptr's contents are echoed to the screen. Additionalechos help to differentiate the value from other commands. Then, to get back the value ptr points to, theuser employs eval.

When the shell invokes eval, it scans the arguments that follow and does a variable substitution on ptr. This leaves the shell with echo $spool. After this, the shell processes this command. It performs anotherscan and expands $spool into its contents, /var/spool. With the second scan complete, the shell prints/var/spool to the terminal.

8.5 Exec Statements

The execbuilt-in command spawns its arguments into a new process in lieu of the currently running shell. Its syntaxis relatively simple: exec command args. When issued, the invoking shell terminates, and command takesover. There is no possibility of returning. Command runs as if it was entered from its own prompt. Thefunction below shows how a script might use exec to run another program after it is complete.

execute_custom_script () { if [ "${CUSTOMSCRIPT}" = "" ]; then exit else if [ -x ${CUSTOMSCRIPT} ]; then echo "Executing ${CUSTOMSCRIPT}" exec ${CUSTOMSCRIPT} fi fi }

8.6 Setting Shell Flags

Set

is another built-in shell command. Its primary function allows the toggling of shell options for controlling ascript's behavior. The list of options is given in the table below. Options are enabled by preceding themwith a dash (-) and are disabled when preceded with a plus (+). Options can be stacked. When used as thefirst command issued in a script, set and its options can be a very useful debuggin tool.

Table 8.6-1. Options to the set commandOption Meaning

-- Do not treat the subsequent arguments as options-a Automatically export all variable defined or modified-e Exit if any command executed has a nonzero exit status-f Disable file name expansion

-hRemember the location of commands used in functions when the functions are defined (same asthe hash command)

-kProcess arguments of the form keyword=value that appear anywhere on the command line andplace them in the environment command

Bourne_Shell_Programming-Robert_P_Sayle.html file:///Z:/VMWare_Shared/Bourne_Shell_Programming-Robert_P_Say...

79 de 102 11/10/2010 16:13

-n Read commands without executing them-t Exit after executing one command-u Issue an error if a null variable is referenced including positional parameters-v Print each shell command line as it is read-x Print each command and its arguments as it is executed with a preceding +

Set provides a second function for scripts. It allows a user to change the positional parameters by giving alist of words as arguments. This can be useful for changing the arguments a script or function shouldprocess, but in practice, it is used rarely. Still, to demonstrate, the commands below show how to do this. The shell, as it is normally invoked in an interactive session, does not have any arguments passed to it. Sothe operator changes this by executing set with a well-known phrase. A while loop processes the newpositional parameters by printing them on the console.

$ set goodbye cruel world $ while [ $# -gt 0] > do > echo ${1} > shift > done goodbye cruel world

8.7 Signals and Traps

UNIX uses a mechanism called signalling to send messages to running processes directing them to changetheir state. A signal is anumber that has a predefined meaning. There are about 30 signals available. Themost commonly used signals are listed below. Signals may be sent by using the kill command orsometimes by entering special control characters (Ctrl-c, Ctrl-z) at the terminal where the program isrunning.

Table 8-7.1. Common UNIX SignalsSignal Name Description Default Action Catchable Blockable

1 SIGHUP Hangup Terminate Yes Yes2 SIGINT Interrupt Terminate Yes Yes9 SIGKILL Kill Terminate No No

11 SIGSEGV Segmentation violation Terminate Yes Yes15 SIGTERM Software termination Terminate Yes Yes17 SIGSTOP Stop Stop No No19 SIGCONT Continue after stop Ignore Yes No

As shown in the table above, some signals can be caught. In other words, a program can detect the signaland then execute some action predefined by the scripter. Handling signals allows scripts to gracefullyterminate. To catch a signal, a script uses the trap command. Trap has a few different acceptable forms,but the general syntax is: trap commands signals.

In this form, trap instructs the shell to catch the signals listed by their numbers in the list of signals. Theshell handles the signals by executing commands, a list of valid UNIX commands. To demonstrate,handletraps catches SIGINT and SIGTERM.

$ cat handletraps #!/bin/sh OUTPUT=out.$$

Bourne_Shell_Programming-Robert_P_Sayle.html file:///Z:/VMWare_Shared/Bourne_Shell_Programming-Robert_P_Say...

80 de 102 11/10/2010 16:13

trap "if [ -f ${OUTPUT} ]; then rm ${OUTPUT}; exit; fi" 2 15 touch ${OUTPUT} while true do sleep 5 done $ handletraps & [1] 836 $ ls handletraps out.836 $ kill -15 836 $ ls handletraps

The program above starts by defining a variable whose value is out.$$. It then sets up the signal handlingwith a trap. The trap catches signals 2 and 15. When these signals are sent to handletraps, the programchecks for the existence of out.$$. If it is present, it removes the file and then terminates. The scriptcontinues by touching the file and then sits in an endless loop.

The user executes handletrapssending it to the background. The shell shows it is running as process number 836. A quick directorylisting shows that the directory now contains the script plus the touched file, out.836. The operator thensends a SIGTERM to handletraps with the kill command. Another directory listing shows that the scriptcaught the signal and ran the if block which removed out.836.

In its other forms, trap can be issued with no arguments. This second form of trap just displays the list ofcurrently handled signals. In its third form, signals can be reset. To do this, the programmer omits the set ofcommands from trap but provides a list of signals. This resets the signals listed to their defaul behavior. Inits fourth form, trapcan be used to ignore a set of signals. This is done by giving an empty command list, denoted by "", to trap. The last form of trapuses a colon as the command list. This causes the parent shell to ignore a signal but its subshells terminate ifthey receive any signals listed in signals.

A couple properties of the trap command are important to remember. Subshells inherit the trap behaviorof their parents. Also, the shell scans command once when it encounters the trap and then again when itprocesses a signal; consequently, it may be necessary to use single quotes to prevent expansion until thesignal is caught.

9. Debugging Hints

Declaring the Shell

A very common mistake among novice scripters is that they forget to declare the shell. The first line of ashell script should always be the shell declaration, #!/bin/sh. Without it, the script automatically inheritsthe shell of the parent program. In other words, when a script does not declare the shell type, it uses thesame shell that is currently being used. This can yield undesirable results if, for example, an operator usesthe cshfor command line processing and then decides to run a Bourne shell script that does not reset the runningshell. The results of neglecting to declare the shell are not necessarily deleterious. In most cases in fact, it

Bourne_Shell_Programming-Robert_P_Sayle.html file:///Z:/VMWare_Shared/Bourne_Shell_Programming-Robert_P_Say...

81 de 102 11/10/2010 16:13

simply results in a program crash with a lot of unexpected errors. But it happens often enough to UNIXusers that it is worthy of mention and should be added to the list of debugging hints.

Tracing Program Execution

Traditional programming languages that require compiling and linking of programs have sophisticated toolsfor debugging. These tools require a program to be compiled with certain flags that allow the program'ssymbols and instructions to be loaded into a run-time debugger. Within the debugger, a programmer can doall sorts of nifty things such as step through a program's execution, set stop points, dump the function stack,and even examine and sometimes change the state of variables. Of course, all of these actions allow theprogrammer to understand, correct, and fine tune the program's execution. Unfortunately, the shell does nothave a readily available debugger, but it does have a tool that is close enough.

By providing options to the setcommand a programmer can trace the steps through a script's execution. All of the options to set were introduced in Section 8.6, Setting Shell Flags. The most useful option is -x. This option causes the shell toprint each command as it is evaluated. This is especially useful to a scripter because the results of actionssuch as variable substitution and file name expansion are shown. Set -x could be further enhanced by combining -v with -x. The -voption instructs the shell to print a command before it is evaluated. The combination obviously allows ascripter to see the command followed by the results of the shell evaluating it. In practice, however, thisresults in an extremely verbose output that may actually be difficult to follow. Readers are encouraged totry both methods and decide which works for the job at hand. The rest of this chapter will only consider theuse of -x.

Now to turn the tracing option on, a script can be executed at the command line by preceding it with sh -x, but a better method is to include the set -xcommand in the script. Placing this command on a line by itself instructs the shell to start tracing on thecommands that follow. To disable it, the scripter simply comments out the line. It is a good habit to includethis line within scripts so that debugging is readily available.

As an example, the mkraddb script is reintroduced. Mkraddb takes a plain text file as its argument andgenerates a RADIUS database from its contents:

#!/bin/sh # # mkraddb: generate the RADIUS configuration of a set of users # #

# Uncomment the following line for debugging set -x

# Global Vars PROG=`basename ${0}` USAGE="${PROG}: usage: ${PROG} users_file" DATE=`date +%m%d%y` RADDBUSERS="raddb.users.${DATE}"

###################################################################### # buildRaddbUsers ######################################################################

buildRaddbUsers () {

cat >>${RADDBUSERS} <<EOF ${name}-out Password = "ascend",

Bourne_Shell_Programming-Robert_P_Sayle.html file:///Z:/VMWare_Shared/Bourne_Shell_Programming-Robert_P_Say...

82 de 102 11/10/2010 16:13

User-Service = Dialout-Framed-User User-Name = ${name}, Framed-Protocol = MPP, Framed-Address = ${ipaddr}, Framed-Netmask = 255.255.255.255, Ascend-Metric = 2, Framed-Routing = None, Ascend-Route-IP = Route-IP-Yes, Ascend-Idle-Limit = 30, Ascend-Send-Auth = Send-Auth-CHAP, Ascend-Send-Passwd = "password", Ascend-Receive-Secret = "password"

EOF

}

###################################################################### # Main ######################################################################

if [ $# -ne 1 ]; then echo ${USAGE} exit 1 fi

# Create the configs while read name ipaddr do if [ "${name}" = "#" -o "${name}" = "" ]; then # continue if the line is a comment or is blank continue fi

# Create the RADIUS users information buildRaddbUsers

done <${1}

# Clean up exit 0

The eighth line is particularly important because it demonstrates the use of set -x. The script lists thiscommand prior to any others. By doing so, it enables tracing on the rest of the script. An astute readermight decide that rather than making it the first command, it could be enabled by making it an option to theprogram. For example, entering mkraddb -dat the command line might cause the script to dynamically execute tracing. This in fact can be done, but itshould be noted that it would also disable the debugging of option handling itself.

For the example, a programmer runs mkraddb with tracing enabled. The script is passed the file userswhose contents are first displayed for comparing with the trace output. The trace then follows. Linenumbers are included with the trace for reference during the explanation of the results; line numbers are notpart of the output when a script is run with set -x. The directory listing is also shown at the end simply todemonstrate successful creation of the RADIUS profiles.

$ ls mkraddb users $ cat users bob 192.168.0.1

Bourne_Shell_Programming-Robert_P_Sayle.html file:///Z:/VMWare_Shared/Bourne_Shell_Programming-Robert_P_Say...

83 de 102 11/10/2010 16:13

joe 172.16.20.2 mary 10.0.25.3 $ mkraddb users 1 ++ basename ./mkraddb 2 + PROG=mkraddb 3 + USAGE=mkraddb: usage: mkraddb users_file 4 ++ date +%m%d%y 5 + DATE=090598 6 + RADDBUSERS=raddb.users.090598 7 + [ 1 -ne 1 ] 8 + read name ipaddr 9 + [ bob = # -o bob = ] 10 + buildRaddbUsers 11 + cat 12 + read name ipaddr 13 + [ joe = # -o joe = ] 14 + buildRaddbUsers 15 + cat 16 + read name ipaddr 17 + [ mary = # -o mary = ] 18 + buildRaddbUsers 19 + cat 20 + read name ipaddr 21 + exit 0 $ ls mkraddb raddb.users.090598 users The trace starts right where the program sets its global variables. Lines one through six show the process. Referring back to the script's code above, the first variable is PROG, which stores the script's name. To setPROG's value, the basenamecommand is run against the zeroeth positional parameter. The trace's first line shows this action. Oneinteresting item to note is the expansion of ${0} into ./mkconfig. The second line displays PROG being set to the results of the first action. Then line three shows USAGE being set. USAGE gets a string that explainshow to properly execute the program. The string's value uses PROG to help create the message as can be seenby comparing the code versus line three's output. Lines four through six demonstrate much of the same. Four and five show that DATE gets assigned the result of executing the date command, and six then uses DATE's value to generate the name of the script's resultant file stored in RADDBUSERS.

Line seven displays the trace of the next code block. After setting the global variables, the script thenchecks that it is executed properly. This is done with the if block that immediately follows the commentthat marks where the main program execution begins. The if blocks tests the command line options to becertain that an argument was passed to the script. Line seven shows the evaluation of the if's test, whichchecks to see if $# is not equal to one. If it is not, the script prints the USAGE error message and terminates sothat the user can correct the problem. In this instance, $# in fact evaluates to one making the test false. Since it is false, the user ran the program correctly, and the script proceeds past the if block into its realwork.

The rest of the example shows what a loop looks like when being traced. It can effectively be seen in blocksof four lines each. Lines eight through eleven are the first execution of the script's while loop. The loopkeys off of the line by line processing of the file passed as the script's first argument. As long as their is anon-empty line in the file, the loop stuffs the line's value into the intermediate variables name and ipaddr. This is exactly what line eight shows. The read command reads from users, finds bob and bob's IP address, stuffs these two values into name and ipaddr, and then returns true to while. Since while receives a true value for its test, the loop proceeds.

The ninth line displays the if block contained within the while loop. This block checks to see if a commentor blank line was read from the file. It does so by comparing the value of name against the strings # and "",

Bourne_Shell_Programming-Robert_P_Sayle.html file:///Z:/VMWare_Shared/Bourne_Shell_Programming-Robert_P_Say...

84 de 102 11/10/2010 16:13

the null string. As can be seen on line nine, bob is definitely not the same as either of these test values, andso the program moves forward. Its next step is to call the function buildRaddbUsers as is done on line 10.

Line eleven is the complete trace of the function's entire execution. All the trace shows is that the scriptcalls the catcommand. This is a far cry from what the function is really doing. Looking at the function's code showsthat it uses catin a complex redirection scheme to create the user's profile. To be more specific, it appends the linesbetween the end of file markers, EOF, into the RADDBUSERS file. During this process, it substitutes the valuesof the intermediate variables name and ipaddrin their appropriate places within the user's RADIUS profile. But all of this action is hidden within the traceas simply an execution of cat.

The trace then shows the next interations of the loop. To summarize, the loop performs the same process onthe next two lines in users. After adding the two other users listed in the file, the loop executes one lastread at line 20 of the trace. The final readreturns a false value to the loop since there are no more lines to read from users. At this point, the loopterminates and picks up at the script's exit point shown by line 21.

As a final note, scripters are reminded to recomment the set -x statement after performing debugging. It isvery easy to do all sorts of work to get a program to run properly and then to forget to turn off the trace. Needless to say, it could be quite confusing to users who then execute the script and get all sorts of outputthat is really nothing more than gibberish to the untrained eye.

Command Line Debugging

One of the primary advantages of shell scripting over traditional programming languages is the ability to runthe same commands given in a script at the command line. Any command in a shell script can be run at theUNIX command prompt and vice versa. This feature makes the shell good for rapid prototyping because aprogrammer can try constructs in a live environment before commiting them to a script.

By the same token, this makes the command line a useful debugging tool. If a scripter suspects a portion ofa script to be causing an error, the programmer may run the command block at the UNIX prompt. It can bedone so in a controlled manner. A scripter can preset variables or functions and then issue commands one ata time. After each step, the scripter has the opportunity to check the state of variables and examine theresults of the previously issued command.

There are many examples given in this book that use the command line to demonstrate programmingtechniques. Rather than show yet another, this subsection finishes by listing some good cases of when thistechnique is useful. Programmers can use command line debugging:

To check a variable's value when it is being set by file name expansion or command substitution.To build filters step-by-step.For building or testing if statements, case blocks, and loops.To determine all the necessary options to a command before entering it in a script.For testing a command block to identify where an error is occurring.

Pausing Program Execution

As stated earlier in this chapter, one of the features of a debugger is the ability to set stop points in aprogram. When a user loads the program into the debugger and runs it, the debugger pauses the program ateach stop point. With the program stopped, the user gets the opportunity to check the program's state andunderstand how well it is functioning.

Bourne_Shell_Programming-Robert_P_Sayle.html file:///Z:/VMWare_Shared/Bourne_Shell_Programming-Robert_P_Say...

85 de 102 11/10/2010 16:13

The shell does not have a debugger to do the same thing, but it can be emulated by a combination of usingset -x and the strategic placement of read statements in the script. Set -x causes the shell to print eachcommand as it executes them from a script, and a read statement forces the program to stop and await userinput. Since the program stops at the readcommand, the programmer gets a chance to review the script's execution up to that point. Hence, a readacts like a stop point. When the user is ready to continue to the next stop point, a bogus value can beentered at the prompt. If no argument follows the read command, the shell simply discards the bogus entry. Actually, no value needs to be provided. Hitting enter suffices. If a programmer solves the problem beinginvestigated, the user may alternatively terminate the program's execution by entering a control character.

To demonstrate, here is a script that counts to five:

$ cat count #!/bin/sh set -x COUNT=0 while [ ${COUNT} -le 5 ] do echo "COUNT=${COUNT}" read COUNT=`expr ${COUNT} + 1` done

Needless to say, this is hardly a complex script, but it shows the technique well enough. The first thing tonotice is that the script enables tracing immediately with set -x. The second is the placement of read for pausing the script. The programmer chooses to embed it within the loop. In fact it halts the program justbefore it changes COUNT's value. Presumably, the programmer wants to watch COUNT by checking its value before attempting to increment it. Executing the script shows the results.

$ count + COUNT=0 + [ 0 -le 5 ] + echo COUNT=0 COUNT=0 + read

++ expr 0 + 1 + COUNT=1 + [ 1 -le 5 ] + echo COUNT=1 COUNT=1 + read

++ expr 1 + 1 + COUNT=2 + [ 2 -le 5 ] + echo COUNT=2 COUNT=2 + read

++ expr 2 + 1 + COUNT=3 + [ 3 -le 5 ] + echo COUNT=3 COUNT=3 + read

++ expr 3 + 1

Bourne_Shell_Programming-Robert_P_Sayle.html file:///Z:/VMWare_Shared/Bourne_Shell_Programming-Robert_P_Say...

86 de 102 11/10/2010 16:13

+ COUNT=4 + [ 4 -le 5 ] + echo COUNT=4 COUNT=4 + read

++ expr 4 + 1 + COUNT=5 + [ 5 -le 5 ] + echo COUNT=5 COUNT=5 + read

++ expr 5 + 1 + COUNT=6 + [ 6 -le 5 ]

In the first block above, the trace shows COUNTbeing set to its initial value of zero. Following that is the first test of the variable in the while loop. The testpasses, so the script prints the the variable's value shown by the echo and the resulting output. The blockends with a read. At that point, the shell pauses execution pending input from an operator. Now theseactions happen much faster than it takes to read this paragraph and compare it against the text above, but byusing the read to stop the script, the operator has a chance to review it. After doing so, the operator hits theenter key shown by the blank line. The program reads the return character entered and resumes itsexecution.

The next trace block is much the same as the first except that it shows the incrementing of COUNT. First, theexpr command adds one to the current value of COUNT, and then the shell assigns the new value to thevariable. The loop tests the new value, still finds that it is within range, prints it, pauses at the read oncemore, and awaits user input. The process repeats until the variable's value passes five and then the scriptcompletes normally.

Null Commands

Sometimes, a good way to debug a script is to do nothing at all. The shell provides a null command in theform of a colon. This command can be useful for checking the various tests in an if block, the switches of acase, or a loop's exit condition. The idea is that a programmer can write the framing of an if, case, or loopbut provide the null command for the internal processing. Then the script can be run to allow the testing ofthe code without sweating the details of the its real function.

To demonstrate, below is the beginning of a script to check the disk usage for a set of user accounts.

$ cat ducheck #!/bin/sh

PROG=`basename ${0}` USAGE="${PROG}: usage: ${PROG} name1 ... nameN"

if [ $# -gt 0 ]; then : else echo ${USAGE} exit 1 fi

The script's author currently has only written the code for verifying that the correct number of arguments arepassed to the program. If they are, then the script executes the null command, breaks out of the if block,

Bourne_Shell_Programming-Robert_P_Sayle.html file:///Z:/VMWare_Shared/Bourne_Shell_Programming-Robert_P_Say...

87 de 102 11/10/2010 16:13

and terminates with no output. Eventually, when the programmer determines the command to gather thedisk usage, the user can replace the colon with the appropriate functions. A test run of ducheck shows that the script behaves accordingly when it is passed arguments.

$ ducheck root securid rsayle $

But for the time being the scripter wishes only to test the proper execution of the if statement when the arguments to the script are incorrect. In particular, the author wants to check that the script prints an errormessage when no arguments have been passed.

$ ducheck ducheck: usage: ducheck name1 ... nameN $

As can be seen, the script handles an empty argument list just as the programmer intended.

Interpretting Errors

To conclude this book, the final section shows examples of syntax errors and how the shell reports them. The reader should note that due to variations in UNIX, the specific error messages may not be exactly thesame. Still, the examples do provide some insight into common mistakes, and hopefully, the wary scriptercan learn them and identify them during debugging.

The examples focus on a script called tgzit. The script's intended function is to take a list of files anddirectories as arguments and then to store them into a compressed tape archive. First, the correct script isshown for comparison against the errant versions.

#!/bin/sh

# global variables PROG=`basename ${0}` USAGE="${PROG}: usage: ${PROG} archive file1 ... fileN"

# check the command line arguments if [ $# -lt 2 ]; then echo ${USAGE} exit 1 fi

# get the archive name from the command line ARCHIVE=${1} shift

# build the archive tar cf ${ARCHIVE}.tar "$@" gzip ${ARCHIVE}.tar mv ${ARCHIVE}.tar.gz ${ARCHIVE}.tgz

By this point, readers should be well versed in shell scripting and should be able to decipher the actionsabove. For the first example, our scripter tries running a slightly different version of tgzit and gets thefollowing error message.

$ tgzit program-archive ./bin ./include ./source ./tgzit: syntax error near unexpected token `fi' ./tgzit: ./tgzit: line 11: `fi'

The shell is specific in the problem here. It states that it encountered the termination of an if block

Bourne_Shell_Programming-Robert_P_Sayle.html file:///Z:/VMWare_Shared/Bourne_Shell_Programming-Robert_P_Say...

88 de 102 11/10/2010 16:13

somewhere near line 11 within the script. This at least points the user at a particular code block. Checkingthe contents of the script shows the error.

$ cat -n tgzit 1 #!/bin/sh 2 3 # global variables 4 PROG=`basename ${0}` 5 USAGE="${PROG}: usage: ${PROG} archive file1 ... fileN" 6 7 # check the command line arguments 8 if [ $# -lt 2 ] then 9 echo ${USAGE} 10 exit 1 11 fi 12 13 # get the archive name from the command line 14 ARCHIVE=${1} 15 shift 16 17 # build the archive 18 tar cf ${ARCHIVE}.tar "$@" 19 gzip ${ARCHIVE}.tar 20 mv ${ARCHIVE}.tar.gz ${ARCHIVE}.tgz

At the eleventh line is the fi statement. Now this is not the error, and it requires a review of the entire ifblock to determine the problem. In this case, the error is fairly subtle. Looking back at the test condition online eight, a careful examination reveals that the scripter failed to terminate the test statement with asemi-colon; hence, the complaint of the unexpected token. The fi was unexpected because the test was notpunctuated correctly.

This example not only demonstrates this common typo, but it also emphasizes that the shell can detect andreport syntax errors, but it does not necessarily report the error's exact location. Programmers mustremember to consider code blocks rather than single lines in order to find problems.

Another common error involves misquoting. Here, a scripter executes tgzit and receives an errorindicating this condition.

$ tgzit program-archive ./bin ./include ./source ./tgzit: unexpected EOF while looking for `"' ./tgzit: ./tgzit: line 20: syntax error

The interesting point to note is that the shell reports the correct problem, but it gives a false indication ofwhere to find the error. Reviewing the source for this buggy version of tgzit, line 20 happens to be the finalline in the script and, in fact, has no quotes whatsoever.

$ cat -n tgzit 1 #!/bin/sh 2 3 # global variables 4 PROG=`basename ${0}` 5 USAGE="${PROG}: usage: ${PROG} archive file1 ... fileN 6 7 # check the command line arguments 8 if [ $# -lt 2 ]; then 9 echo ${USAGE} 10 exit 1 11 fi

Bourne_Shell_Programming-Robert_P_Sayle.html file:///Z:/VMWare_Shared/Bourne_Shell_Programming-Robert_P_Say...

89 de 102 11/10/2010 16:13

12 13 # get the archive name from the command line 14 ARCHIVE=${1} 15 shift 16 17 # build the archive 18 tar cf ${ARCHIVE}.tar "$@" 19 gzip ${ARCHIVE}.tar 20 mv ${ARCHIVE}.tar.gz ${ARCHIVE}.tgz

Quoting errors can be extremely difficult to find in especially large scripts because the shell does notpin-point the error's location. Fortunately, for this script, it is easy to see that at the end of the fifth line, thescripter forgot to close the double-quotes that set the USAGE variable. Still the shell does not report the erroruntil after reading the entire script because it is perfectly legal for quoted strings to extend over multiplelines. Unfortunately for scripters this fact makes it hard to find such errors. They must either inspect ascript by eye or hopefully can employ a command in their text editor that shows matching opening andclosing punctuation.

The next example is not really a syntax error as much as it is logical, but it is a frequent enough occurrencethat it warrants attention. In this case, the programmer forgets that the proper usage of the script calls for atleast two arguments: the first being the archive in which to store files and the rest being the files to archive.

$ cat tgzit #!/bin/sh

# global variables PROG=`basename ${0}` USAGE="${PROG}: usage: ${PROG} archive file1 ... fileN"

# check the command line arguments if [ $# -lt 1 ]; then echo ${USAGE} exit 1 fi

# get the archive name from the command line ARCHIVE=${1} shift

# build the archive tar cf ${ARCHIVE}.tar "$@" gzip ${ARCHIVE}.tar mv ${ARCHIVE}.tar.gz ${ARCHIVE}.tgz

The problem is in the test for the correct number of arguments. The scripter incorrectly checks for at leastone argument. The program assumes that the first argument is the archive to be built. If a user executes thetgzit with just one argument, tar has nothing to put in the archive.

$ tgzit program-archive tar: Cowardly refusing to create an empty archive Try `tar --help' for more information. prog-archive.tar: No such file or directory mv: prog-archive.tar.gz: No such file or directory

The script itself does not yield the error. Instead, the tar command notes that it has nothing to do. The scriptcontinues past the tar statement, but the remaining commands also have nothing to do since the archive doesnot exist. They complain accordingly. Scripters must be careful to check and test their scripts' arguments.

Another common error scripters make is simply a typo. It is very easy to forget to close punctuation

Bourne_Shell_Programming-Robert_P_Sayle.html file:///Z:/VMWare_Shared/Bourne_Shell_Programming-Robert_P_Say...

90 de 102 11/10/2010 16:13

properly or to create malformed statements all together.

$ cat tgzit #!/bin/sh

# global variables PROG=`basename ${0}` USAGE="${PROG}: usage: ${PROG} archive file1 ... fileN"

# check the command line arguments if [ $# -lt 2]; then echo ${USAGE} exit 1 fi

# get the archive name from the command line ARCHIVE=${1} shift

# build the archive tar cf ${ARCHIVE}.tar "$@" gzip ${ARCHIVE}.tar mv ${ARCHIVE}.tar.gz ${ARCHIVE}.tgz

The syntax error here is quite subtle. The shell, however, still catches it.

$ tgzit program-archive ./bin ./include ./source ./tgzit: [: missing `]'

The shell reports that there is a problem with a test. Luckily, for this script, there is only one test toexamine; namely, the if block that checks the program's arguments. The scripter forgot to place a spacebetween the two and the closing square bracket. Had the script contained multiple tests, the error might bequite elusive. The only way to truly find the problem is to search through each test.

As a final note about debugging, the theme of this chapter is that although the Bourne shell detects andreports syntax errors, it is not very helpful in indicating exactly where they occur. Scripters are herebywarned. But with the hints given above and with plenty of practice, a good shell scripter can catch themrather quickly.

A. Shell Built-in Command Reference

The following text is taken directly from the Bourne shell manual page (sh(1)). It originated from a SunSPARCstation running the Solaris 2.4 operating system, an SVR4 compliant UNIX. According to the manpage, the information presented was last modified on October 11, 1993. Regardless of this fact, thesecommands should be consistent with any SVR4 or POSIX compliant system. Note that some descriptionswill point to a man page by listing the command name followed by a number enclosed in parentheses. Thereader is urged to refer to the declared man page for further reference.

. filenameRead and execute commands from filename and return. The search path specified by PATH is used tofind the directory containing filename.

:

Bourne_Shell_Programming-Robert_P_Sayle.html file:///Z:/VMWare_Shared/Bourne_Shell_Programming-Robert_P_Say...

91 de 102 11/10/2010 16:13

No effect; the command does nothing. A zero exit code is returned.

break [ n ]Exit from the enclosing for or while loop, if any. If n is specified, break n levels.

continue [ n ]Resume the next iteration of the enclosing for or while loop. If n is specified, resume at the n-thenclosing loop.

cd [ argument ]Change the current directory to argument. The shell parameter HOME is the default argument. Theshell parameter CDPATH defines the search path for the directory containing argument. Alternativedirectory names are separated by a colon (:). The default path is null (specifying the current directory).Note: The current directory is specified by a null path name, which can appear immediately after theequal sign or between the colon delimiters anywhere else in the path list. If argument begins with a /the search path is not used. Otherwise, each directory in the path is searched for argument.

echo [ argument ... ] Echo arguments. See echo(1) for usage and description.

eval [ argument ... ]The arguments are read as input to the shell and the resulting command(s) executed.

exec [ argument ... ]The command specified by the arguments is executed in place of this shell without creating a newprocess. Input/output arguments may appear and, if no other arguments are given, cause the shellinput/output to be modified.

exit [ n ]Causes a shell to exit with the exit status specified by n. If n is omitted the exit status is that of the lastcommand executed (an EOF will also cause the shell to exit.)

export [ name ... ]The given names are marked for automatic export to the environment of subsequently executedcommands. If no arguments are given, variable names that have been marked for export during thecurrent shell's execution are listed. (Variable names exported from a parent shell are listed only if theyhave been exported again during the current shell's execution.) Function names are not exported.

getoptsUse in shell scripts to support command syntax stan- dards (see intro(1)); it parses positionalparameters and checks for legal options. See getoptcvt(1) for usage and description.

hash [ -r ] [ name ... ]For each name, the location in the search path of the command specified by name is determined andremembered by the shell. The -r option causes the shell to forget all remembered locations. If noarguments are given, information about remembered commands is presented. Hits is the number oftimes a command has been invoked by the shell process. Cost is a measure of the work required tolocate a command in the search path. If a command is found in a "relative" directory in the searchpath, after changing to that directory, the stored location of that command is recalculated. Com-mands for which this will be done are indicated by an asterisk (*) adjacent to the hits information.Cost will be incremented when the recalculation is done.

newgrp [ argument ]Equivalent to exec newgrp argument. See newgrp(1M) for usage and description.

pwdPrint the current working directory. See pwd(1) for usage and description.

Bourne_Shell_Programming-Robert_P_Sayle.html file:///Z:/VMWare_Shared/Bourne_Shell_Programming-Robert_P_Say...

92 de 102 11/10/2010 16:13

read name ...One line is read from the standard input and, using the internal field separator, IFS (normally space ortab), to delimit word boundaries, the first word is assigned to the first name, the second word to thesecond name, etc., with leftover words assigned to the last name. Lines can be continued using\newline. Characters other than newline can be quoted by preceding them with a backslash. Thesebackslashes are removed before words are assigned to names, and no interpretation is done on thecharacter that follows the backslash. The return code is 0, unless an EOF is encountered.

readonly [ name ... ]The given names are marked readonly and the values of the these names may not be changed bysubsequent assignment. If no arguments are given, a list of all readonly names is printed.

return [ n ]Causes a function to exit with the return value specified by n. If n is omitted, the return status is that ofthe last command executed.

set [ --aefhkntuvx [ argument ... ] ]-a Mark variables which are modified or created for export. -e Exit immediately if a command exitswith a nonzero exit status. -f Disable file name generation. -h Locate and remember functioncommands as functions are defined (function commands are normally located when the function isexecuted). -k All keyword arguments are placed in the environment for a command, not just those thatprecede the command name. -n Read commands but do not execute them. -t Exit after reading andexecuting one command. -u Treat unset variables as an error when substituting. -v Print shell inputlines as they are read. -x Print commands and their arguments as they are executed. -- Do not changeany of the flags; useful in setting $1 to -. Using + rather than - causes these flags to be turned off.These flags can also be used upon invocation of the shell. The current set of flags may be found in $- .The remaining arguments are positional parameters and are assigned, in order, to $1, $2, .... If noarguments are given the values of all names are printed.

shift [ n ]The positional parameters from $n+1 ... are renamed $1 ... . If n is not given, it is assumed to be 1.

stop pid ...Halt execution of the process number pid. (see ps(1)).

testEvaluate conditional expressions. See test(1) for usage and description.

timesPrint the accumulated user and system times for processes run from the shell.

trap [ argument ] [ n ] ...The command argument is to be read and executed when the shell receives numeric or symbolicsignal(s) (n). (Note: argument is scanned once when the trap is set and once when the trap is taken.)Trap commands are executed in order of signal number or corresponding symbolic names. Anyattempt to set a trap on a signal that was ignored on entry to the current shell is ineffective. An attemptto trap on signal 11 (memory fault) produces an error. If argument is absent all trap(s) n are reset totheir original values. If argument is the null string this signal is ignored by the shell and by thecommands it invokes. If n is 0 the command argument is executed on exit from the shell. The trapcommand with no arguments prints a list of commands associated with each signal number.

type [ name ... ]For each name, indicate how it would be interpreted if used as a command name.

ulimit [ -[HS][a | cdfnstv] ] OR ulimit [ -[HS][c | d | f | n | s | t | v] ] limitulimit prints or sets hard or soft resource limits. These limits are described in getrlimit(2). If limit isnot present, ulimit prints the specified limits. Any number of limits may be printed at one time. The -a

Bourne_Shell_Programming-Robert_P_Sayle.html file:///Z:/VMWare_Shared/Bourne_Shell_Programming-Robert_P_Say...

93 de 102 11/10/2010 16:13

option prints all limits. If limit is present, ulimit sets the specified limit to limit. The string unlimitedrequests the largest valid limit. Limits may be set for only one resource at a time. Any user may set asoft limit to any value below the hard limit. Any user may lower a hard limit. Only a super-user mayraise a hard limit; see su(1M). The -H option specifies a hard limit. The - S option specifies a softlimit. If neither option is speci- fied, ulimit will set both limits and print the soft limit. The followingoptions specify the resource whose limits are to be printed or set. If no option is specified, the file sizelimit is printed or set. -c maximum core file size (in 512-byte blocks) -d maximum size of datasegment or heap (in kbytes) -f maximum file size (in 512-byte blocks) -n maximum file descriptorplus 1 -s maximum size of stack segment (in kbytes) -t maximum CPU time (in seconds) -v maximumsize of virtual memory (in kbytes).

umask [ nnn ]The user file-creation mask is set to nnn (see umask(1)). If nnn is omitted, the current value of themask is printed.

unset [ name ... ]For each name, remove the corresponding variable or function value. The variables PATH, PS1, PS2,MAILCHECK, and IFS cannot be unset.

wait [ n ]Wait for your background process whose process id is n and report its termination status. If n isomitted, all your shell's currently active background processes are waited for and the return code willbe zero.

C. Index

From the list below, select the letter that is the first character of the topic you want to find:A B C D E F G H I J K L M N O P Q R S T U V W X Y Z

.

;

;;

()

{}

[]

<

<<

<&

<&-

>

>>

>&

>&-

&

&&

|

||

''

""

``

\

Bourne_Shell_Programming-Robert_P_Sayle.html file:///Z:/VMWare_Shared/Bourne_Shell_Programming-Robert_P_Say...

94 de 102 11/10/2010 16:13

#

A

and, -aarguments

functions as arguments to test conditionsof for loopsof functions

arithmeticusing bcusing expr

B

background, &back quotes, ``

see command substitutionbc

reading from filesbreak

sh man page description

C

case

double semi-colons, ;;, in case statementparentheses, (), in case statementsregular expressions in case statementssyntax

cd

sh man page descriptioncommand line debuggingcommand line optionscolon, :

sh man page descriptioncommand substitution, ``

debugging scriptsfor loop argumenteffects of evalnesting commands

comments, #inlineline

continue

sh man page descriptioncurly braces, {}

function delimiterdereferencing variablesusing for grouping v. variables

D

debugging scripts

Bourne_Shell_Programming-Robert_P_Sayle.html file:///Z:/VMWare_Shared/Bourne_Shell_Programming-Robert_P_Say...

95 de 102 11/10/2010 16:13

command line debuggingexamples of when to use

declaring the shellnull commandsinterpreting errors

examplesmalformed ifmalformed testmissing argumentsmissing quotes

how the shell reports syntax errorspausing a script's execution

continuing a paused scriptexampleterminating a paused scriptusing set -x and read

tracing a script's executionenabling with a script optiondisabling tracingtracing exampleusing in conjunction with pausingusing set -xusing set -xvusing sh -x

do

in for loopsin until loopsin while loops

done

in for loopsin until loopsin while loops

dotsh man page description

double ampersand, &&double quotes, ""

escaping metacharacterspatterns containing white spacetesting stringsusing with functions as arguments to test conditionsusing with escapeusing with single quotes

double semi-colons, ;;double vertical bar, ||

E

echo

sh man page descriptionelif

else

environmentvariables

escape, \escaping metacharacters

Bourne_Shell_Programming-Robert_P_Sayle.html file:///Z:/VMWare_Shared/Bourne_Shell_Programming-Robert_P_Say...

96 de 102 11/10/2010 16:13

line continuationusing with double quotes

esac

eval

behaviordefinitionsh man page description

exec

definitionscript I/O

exit

handling errors withsh man page description

export

sh man page descriptionexpr

F

false

fi

file descriptorsredirectionstderrstdinstdout

filename expansionas for loop argumenteffects of evalspecial characters

for loopargument list generatingbehaviordefinitionintermediate variable usagepositional parameters usagesyntax

functions

as an argument to test conditions quotingdeclaration argument list body delimiter label rulesdefinitioninvokingmodularity including in multiple scriptsnestingperformance improving script performance vs sourcing

Bourne_Shell_Programming-Robert_P_Sayle.html file:///Z:/VMWare_Shared/Bourne_Shell_Programming-Robert_P_Say...

97 de 102 11/10/2010 16:13

vs subscriptspropertiesreturn value checking with $? defaultreturning from a functionscope of nested functions of variables declared by functions

G

getopts

behaviordefinitionOPTARG variableprocessing command line optionssh man page descriptionsyntaxusage

H

hash

sh man page description

I

if

nesting if blockssyntaxusage

I/O devices/dev/console

/dev/null

/dev/tty

standard I/O

J

job control

K

L

loopsfor loopsloop control break continuewhile loops

Bourne_Shell_Programming-Robert_P_Sayle.html file:///Z:/VMWare_Shared/Bourne_Shell_Programming-Robert_P_Say...

98 de 102 11/10/2010 16:13

until loops

M

metacharacterstable of

N

newgrpsh man page description

O

operatorsarithmeticfile

negation oftable of

integrallist of

logicaland, -aor, -ousage

stringtable of

optionsarguments todefinitionforms separated stackedprocessing getopts positional parametersshell options, set

OPTARG

or, -o

P

parameterspositional as function arguments as for loop arguments changing with set handling command line options withspecial

parameter substitutionparentheses, ()

role in function declarationusage in case statements

period

Bourne_Shell_Programming-Robert_P_Sayle.html file:///Z:/VMWare_Shared/Bourne_Shell_Programming-Robert_P_Say...

99 de 102 11/10/2010 16:13

See dotpipe, |

filtering withpwd

sh man page description

Q

quotingforms oftesting strings with quotes

R

read

pausing script executionreading from filessh man page description

readonly

sh man page descriptionredirection

file descriptors stderr into stdoutinput file based, < interactive, << syntaxloopsoutput creating new files with, > appending to existing files, >> syntaxscript I/O

regular expressionsusing in case statementsutilitiestable of

return

checking return value with $?default valuefunction controlsh man page descriptionsyntax

S

scriptsargument forbodycomparison to essaydeclaring shell to useterminating

set

options

Bourne_Shell_Programming-Robert_P_Sayle.html file:///Z:/VMWare_Shared/Bourne_Shell_Programming-Robert_P_Say...

100 de 102 11/10/2010 16:13

positional parameterssh man page descriptiontracing scripts

semicolonshift

in while loopssh man page description

single quotes, ''escaping metacharacterspatterns containing white spaceusing with double quotes

signalsdefinitiondetection ofhandling with traplist ofsending to a program

standard I/Oclosingfile descriptorsredirection reading files with script I/O stderr into stdout using exec using loopsstderrstdinstdout

stop

sh man page descriptionsubscripts

executing with execperformance, vs functionsperformance, vs sourcingsourcing

synchronization

T

test

combining multiple tests into onefile testsforcing a test result

colon, :false

true

functions as argumentsinteger testssh man page descriptionsquare brackets as test, []

syntaxvs test command

string testswhile loops

Bourne_Shell_Programming-Robert_P_Sayle.html file:///Z:/VMWare_Shared/Bourne_Shell_Programming-Robert_P_Say...

101 de 102 11/10/2010 16:13

until loopstimes

sh man page descriptiontrap

behaviordefinitionforms ofpropertiessh man page descriptionsignalssyntax

true

type

sh man page description

U

ulimit

sh man page descriptionumask

sh man page descriptionunset

sh man page descriptionuntil loop

behaviordefinitionsyntaxtest condition

V

variablesenvironment variablesexportingpositional parameterspropertiesdata typespermissionsread-onlyread-writescope when declared within a functionspecial parametersstoringcommand outputfilename expansionquoteswhite spacevalueassignmentusing parameter substitution for defaultsusing readdereferencingusing braces

Bourne_Shell_Programming-Robert_P_Sayle.html file:///Z:/VMWare_Shared/Bourne_Shell_Programming-Robert_P_Say...

102 de 102 11/10/2010 16:13

W

wait

sh man page descriptionwhile loop

behaviordefinitionshiftingsyntaxtest condition

X

Y

Z