O'Reilly logo

Learning the bash Shell, Second Edition by Bill Rosenblatt, Cameron Newham

Stay ahead with the world's most comprehensive technology and business learning platform.

With Safari, you learn the way you learn best. Get unlimited access to videos, live online training, learning paths, books, tutorials, and more.

Start Free Trial

No credit card required

Chapter 4. Basic Shell Programming

If you have become familiar with the customization techniques we presented in the previous chapter, you have probably run into various modifications to your environment that you want to make but can’t—yet. Shell programming makes these possible.

bash has some of the most advanced programming capabilities of any command interpreter of its type. Although its syntax is nowhere near as elegant or consistent as that of most conventional programming languages, its power and flexibility are comparable. In fact, bash can be used as a complete environment for writing software prototypes. [48]

Some aspects of bash programming are really extensions of the customization techniques we have already seen, while others resemble traditional programming language features. We have structured this chapter so that if you aren’t a programmer, you can read this chapter and do quite a bit more than you could with the information in the previous chapter. Experience with a conventional programming language like Pascal or C is helpful (though not strictly necessary) for subsequent chapters. Throughout the rest of the book, we will encounter occasional programming problems, called tasks, whose solutions make use of the concepts we cover.

Shell Scripts and Functions

A script, which is 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 Figure 4-1.a, typing source alice causes the two commands to run in the same shell, just as if you had typed them in by hand. Figure 4-1.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 Figure 4-1.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 Figure 4-1.c and Figure 4-1.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.

Ways to run a shell script

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.

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. [49]We will look at declare in more detail in Chapter 6.

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, that 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 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. If you supply more than one argument to type, it will print the information for each one in turn.

type has three 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 -all. 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 -path 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 default output from type is verbose; it will give you the full definition for an alias or function. By using the -type option, you can restrict this to a single word descriptor: alias, keyword, function, builtin, or file. For example:

$ type -type bash
file
$ type -type if
keyword

The -type option can also be used with -all.

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.



[48] An example of this (a compiler for a simple language) is provided in the examples archive for this book. See Appendix E for instructions on how to obtain the archive.

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

With Safari, you learn the way you learn best. Get unlimited access to videos, live online training, learning paths, books, interactive tutorials, and more.

Start Free Trial

No credit card required