Previous Section  < Day Day Up >  Next Section

4.1. Shell Scripts and Functions

A script (a file that contains shell commands) is a shell program. Your .bash_profile and environment files, discussed in the previous chapter, are shell scripts.

You can create a script using the text editor of your choice. Once you have created one, there are two ways to run it. One, which we have already covered, is to type source scriptname. This causes the commands in the script to be read and run as if you typed them in.

The second way to run a script is simply to type its name and hit RETURN, just as if you were invoking a built-in command. This, of course, is the more convenient way. This method makes the script look just like any other UNIX command, and in fact several "regular" commands are implemented as shell scripts (i.e., not as programs originally written in C or some other language), including spell, man on some systems, and various commands for system administrators. The resulting lack of distinction between "user command files" and "built-in commands" is one factor in UNIX's extensibility and, hence, its favored status among programmers.

You can run a script by typing its name only if the directory where the script is located is in your command search path, or . (the current directory) is part of your command search path, i.e., the script's directory path (as discussed in Chapter 3). If these aren't in your path, you must type ./scriptname, which is really the same thing as typing the script's absolute pathname (see Chapter 1).

Before you can invoke the shell script by name, you must also give it "execute" permission. If you are familiar with the UNIX filesystem, you know that files have three types of permissions (read, write, and execute) and that those permissions apply to three categories of user (the file's owner, a group of users, and everyone else). Normally, when you create a file with a text editor, the file is set up with read and write permission for you and read-only permission for everyone else.

Therefore you must give your script execute permission explicitly, by using the chmod command. The simplest way to do this is to type:

$ chmod +x  scriptname 

Your text editor will preserve this permission if you make subsequent changes to your script. If you don't add execute permission to the script and you try to invoke it, the shell will print the message:

scriptname: Permission denied

But there is a more important difference between the two ways of running shell scripts. While using source causes the commands in the script to be run as if they were part of your login session, the "just the name" method causes the shell to do a series of things. First, it runs another copy of the shell as a subprocess; this is called a subshell. The subshell then takes commands from the script, runs them, and terminates, handing control back to the parent shell.

Figure 4-1 shows how the shell executes scripts. Assume you have a simple shell script called alice that contains the commands hatter and gryphon. In .a, typing source alice causes the two commands to run in the same shell, just as if you had typed them in by hand. .b shows what happens when you type just alice: the commands run in the subshell while the parent shell waits for the subshell to finish.

You may find it interesting to compare this with the situation in .c, which shows what happens when you type alice &. As you will recall from Chapter 1, the & makes the command run in the background, which is really just another term for "subprocess." It turns out that the only significant difference between .c and .b is that you have control of your terminal or workstation while the command runs—you need not wait until it finishes before you can enter further commands.

Figure 4-1. Ways to run a shell script


There are many ramifications to using subshells. An important one is that the exported environment variables that we saw in the last chapter (e.g., TERM, EDITOR, PWD) are known in subshells, whereas other shell variables (such as any that you define in your .bash_profile without an export statement) are not.

Other issues involving subshells are too complex to go into now; see Chapter 7 and Chapter 8 for more details about subshell I/O and process characteristics, respectively. For now, just bear in mind that a script normally runs in a subshell.

4.1.1. Functions

bash's function feature is an expanded version of a similar facility in the System V Bourne shell and a few other shells. A function is sort of a script-within-a-script; you use it to define some shell code by name and store it in the shell's memory, to be invoked and run later.

Functions improve the shell's programmability significantly, for two main reasons. First, when you invoke a function, it is already in the shell's memory; therefore a function runs faster. Modern computers have plenty of memory, so there is no need to worry about the amount of space a typical function takes up. For this reason, most people define as many commonly used functions as possible rather than keep lots of scripts around.

The other advantage of functions is that they are ideal for organizing long shell scripts into modular "chunks" of code that are easier to develop and maintain. If you aren't a programmer, ask one what life would be like without functions ( also called procedures or subroutines in other languages) and you'll probably get an earful.

To define a function, you can use either one of two forms:

function functname{

    shell commands}

or:

functname  ( ) 

{

    shell commands } 

There is no functional difference between the two. We will use both forms in this book. You can also delete a function definition with the command unset -f functname.

When you define a function, you tell the shell to store its name and definition (i.e., the shell commands it contains) in memory. If you want to run the function later, just type in its name followed by any arguments, as if it were a shell script.

You can find out what functions are defined in your login session by typing declare -f. The shell will print not just the names but the definitions of all functions, in alphabetical order by function name. Since this may result in long output, you might want to pipe the output through more or redirect it to a file for examination with a text editor. If you just want to see the names of the functions, you can use declare -F.[1] We will look at declare in more detail in Chapter 6.

[1] The -F option is not available in versions of bash prior to 2.0.

Apart from the advantages, there are two important differences between functions and scripts. First, functions do not run in separate processes, as scripts do when you invoke them by name; the "semantics" of running a function are more like those of your .bash_profile when you log in or any script when invoked with the source command. Second, if a function has the same name as a script or executable program, the function takes precedence.

This is a good time to show the order of precedence for the various sources of commands when you type a command to the shell:

  1. Aliases

  2. Keywords such as function and several others, like if and for, which we will see in Chapter 5

  3. Functions

  4. Built-ins like cd and type

  5. Scripts and executable programs, for which the shell searches in the directories listed in the PATH environment variable

Thus, an alias takes precedence over a function or a script with the same name. You can, however, change the order of precedence by using the built-ins command, builtin, and enable. This allows you to define functions, aliases, and script files with the same names, and select which one you want to execute. We'll examine this process in more detail in the section on command-line processing in Chapter 7.

If you need to know the exact source of a command, there are options to the type built-in command that we saw in Chapter 3. type by itself will print how bash would interpret the command, based on the search locations listed above. If you supply more than one argument to type, it will print the information for each command in turn. If you had a shell script, a function, and an alias all called dodo, type would tell you that dodo, as an alias, would be used if you typed dodo.

type has several options that allow you to find specific details of a command. If you want to find out all of the definitions for dodo you can use type -a. This will produce output similar to the following:

$ type -all dodo

dodo is aliased to `echo "Everybody has won, and all must have prizes"'

dodo is a function

dodo ( )

{

    echo "Everybody has won, and all must have prizes"

}

dodo is ./dodo

It is also possible to restrict the search to commands that are executable files or shell scripts by using the -p option. If the command as typed to bash executes a file or shell script, the path name of the file is returned; otherwise, nothing is printed.

The -P option forces type to look for executable files or shell scripts even if the result of -t would not return file.

A further option, -f, suppresses shell function lookup, i.e., only keywords, files and aliases will be returned.[2]

[2] The options -f and -P are not available in versions of bash prior to 2.05b.

The default output from type is verbose; it will give you the full definition for an alias or function. By using the -t option, you can restrict this to a single word descriptor: alias, keyword, function, builtin, or file. For example:

$ type -t bash

file

$ type -t if

keyword

The -t option can also be used with all other options.

We will refer mainly to scripts throughout the remainder of this book, but unless we note otherwise, you should assume that whatever we say applies equally to functions.

    Previous Section  < Day Day Up >  Next Section