2.5. Writing and Running Shell Scripts

A shell script is a simple text file and can be written using any Unix text editor. Some discussion of Unix text editors can be found in the section called “Text Editors”.

Caution

Recall from the section called “Unix vs. Windows Text Files” that Windows uses a slightly different text file format than Unix. Hence, editing Unix shell scripts in a Windows editor can be problematic. Users are advised to do all of their editing on a Unix machine rather than write programs and scripts on Windows and transfer them to Unix.

Shell scripts often contain very complex commands that are wider than a typical terminal window. A command can be continued on the next line by typing a backslash (\) immediately before pressing Enter. This feature is present in all Unix shells. Of course, it can be used on an interactive CLI as well, but is far more commonly used in scripts to improve readability.

printf "%s %s\n" "This command is too long to fit in a single 80-column" \
    "terminal window, so we break it up with a backslash."
	

It's not a bad idea to name the script with a file name extension that matches the shell it uses. This just makes it easier to see which shell each of your script files use. Table 2.1, “Conventional script file name extensions” shows conventional file name extensions for the most common shells. However, if a script is to be installed into the PATH so that it can be used as a regular command, it is usually given a name with no extension. Most users would rather type "cleanup" than "cleanup.bash".

Like all programs, shell scripts should contain comments to explain what the commands in it are doing. In all Unix shells, anything from a '#' character to the end of a line is considered a comment and ignored by the shell.

# Print the name of the host running this script
hostname
	

Table 2.1. Conventional script file name extensions

ShellExtension
Bourne Shell.sh
C shell.csh
Bourne Again Shell.bash
T-shell.tcsh
Korn Shell.ksh
Z-shell.zsh

Practice Break

Using your favorite text editor, enter the following text into a file called hello.sh.

  1. The first step is to create the file containing your script, using any text editor, such as nano:

    shell-prompt: nano hello.sh
    		

    Once in the text editor, add the following text to the file:

    printf "Hello!\n"
    printf "I am a script running on a computer called `hostname`\n"
    		

    After typing the above text into the script, save the file and exit the editor. If you are using nano, the menu at the bottom of the screen tells you how to save (write out, Ctrl+o) and exit (Ctrl+x).

  2. Once we've written a script, we need a way to run it. A shell script is simply input to a shell program. Like many Unix programs, shells take their input from the standard input by default. We could, therefore, use redirection to make it read the file via standard input:

    shell-prompt: sh < hello.sh
    		

Sync-point: Instructor: Make sure everyone in class succeeds at this exercise before moving on.

Since the shell normally reads commands from the standard input, the above command will "trick" sh into reading its commands from the file hello.sh.

However, Unix shells and other scripting languages provide a more convenient method of indicating what program should interpret them. If we add a special comment, called a shebang line to the top of the script file and make the file executable using chmod, the script can be executed like a Unix command. We can then simply type its name at the shell prompt, and another shell process will start up and run the commands in the script. If the directory containing such a script is included in $PATH, then the script can be run from any current working directory, just like ls, cp, etc.

The shebang line consists of the string "#!" followed by the full path name of the command that should be used to execute the script, or the path /usr/bin/env followed by the name of the command. For example, both of the following are valid ways to indicate a Bourne shell (sh) script, since /bin/sh is the Bourne shell command.

#!/bin/sh
	
#!/usr/bin/env sh
	

When you run a script as a command, by simply typing its file name at the Unix command-line, a new shell process is created to interpret the commands in the script. The shebang line specifies which program is invoked for the new shell process that runs the script.

Note

The shebang line must begin at the very first character of the script file. There cannot even be blank lines above it or white space to the left of it. The "#!" is an example of a magic number. Many files begin with a 16-bit (2-character) code to indicate the type of the file. The "#!" indicates that the file contains some sort of interpreted language program, and the characters that follow will indicate where to find the interpreter.

The /usr/bin/env method is used for add-on shells and other interpreters, such as Bourne-again shell (bash), Korn shell (ksh), and Perl (perl). These interpreters may be installed in different directories on different Unix systems. For example, bash is typically found in /bin/bash on Linux systems, /usr/local/bin/bash on FreeBSD systems, and /usr/pkg/bin/bash on NetBSD. The T-shell is found in /bin/tcsh on FreeBSD and CentOS Linux and in /usr/bin/tcsh on Ubuntu Linux.

The env command is found in /usr/bin/env on virtually all Unix systems. Hence, this provides a method for writing shell scripts that are portable across Unix systems (i.e. they don't need to be modified to run on different Unix systems).

Note

Every script or program should be tested on more than one platform (e.g. BSD, Cygwin, Linux, Mac OS X, etc.) immediately, in order to shake out bugs before they cause problems.

The fact that a program works fine on one operating system and CPU does not mean that it's free of bugs.

By testing it on other operating systems, other hardware types, and with other compilers or interpreters, you will usually expose bugs that will seem obvious in hindsight.

As a result, the software will be more likely to work properly when time is critical, such as when there is an imminent deadline approaching and no time to start over from the beginning after fixing bugs. Encountering software bugs at times like these is very stressful and usually easily avoided by testing the code on multiple platforms in advance.

Bourne shell (sh) is present and installed in /bin on all Unix-compatible systems, so it's safe to hard-code #!/bin/sh is the shebang line.

C shell (csh) is not included with all systems, but is virtually always in /bin if present, so it is generally safe to use #!/bin/csh as well.

For all other interpreters it's best to use #!/usr/bin/env.

#!/bin/sh           (OK and preferred)
	
#!/bin/csh          (Generally OK)
	
#!/bin/bash         (Bad idea: Not portable)
	
#!/usr/bin/perl     (Bad idea: Not portable)
	
#!/usr/bin/python   (Bad idea: Not portable)
	
#!/bin/tcsh         (Bad idea: Not portable)
	
#!/usr/bin/env bash     (This is portable)
	
#!/usr/bin/env tcsh     (This is portable)
	
#!/usr/bin/env perl     (This is portable)
	
#!/usr/bin/env python   (This is portable)
	

Even if your system comes with /bin/bash and you don't intend to run the script on any other system, using /usr/bin/env is still a good idea, because you or someone else may want to use a newer version of bash that's installed in a different location. The same applies to other scripting languages such as C-shell, Perl, Python, etc.

Example 2.1. A Simple Bash Script

Suppose we want to write a script that is always executed by bash, the Bourne Again Shell. We simply need to add a shebang line indicating the path name of the bash executable file.

shell-prompt: nano hello.sh
	    

Enter the following text in the editor. Then save the file and exit back to the shell prompt.

#!/usr/bin/env bash

# A simple command in a shell script
printf "Hello, world!\n"
	    

Now, make the file executable and run it:

shell-prompt: chmod a+rx hello.sh   # Make the script executable
shell-prompt: ./hello.sh            # Run the script as a command
	    

Example 2.2. A Simple T-shell Script

Similarly, we might want to write a script that is always executed by tcsh, the TENEX C Shell. We simply need to add a shebang line indicating the path name of the tcsh executable file.

shell-prompt: nano hello.tcsh
	    
#!/usr/bin/env tcsh

# A simple command in a shell script
printf "Hello, world!\n"
	    
shell-prompt: chmod a+rx hello.tcsh   # Make the script executable
shell-prompt: ./hello.tcsh            # Run the script as a command
	    

Note

Many of the Unix commands you use regularly may actually be scripts rather than binary programs.

Note

There may be cases where you cannot make a script executable. For example, you may not own it, or the file system may not allow executables, to prevent users from running programs where they shouldn't.

In these cases, we can simply run the script as an argument to an appropriate shell. For example:

shell-prompt: sh hello.sh
shell-prompt: bash hello.bash
shell-prompt: csh hello.csh
shell-prompt: tcsh hello.tcsh
shell-prompt: ksh hello.ksh
	

Note also that the shebang line in a script is ignored when you explicitly run a shell this way. The content of the script will be interpreted by the shell that you have manually invoked, regardless of what the shebang line says.

Scripts that you create and intend to use regularly can be placed in your PATH, so that you can run them from anywhere. A common practice among Unix users is to create a directory called ~/bin, and configure the login environment to that this directory is always in the PATH. Programs and scripts placed in this directory can then be used like any other Unix command, without typing the full path name.

2.5.1. Self-test

  1. What tools can be used to write shell scripts?
  2. Is it a good idea to write Unix shell scripts under Windows? Why or why not?
  3. After creating a new shell script, what must be done in order to make it executable like a Unix command?
  4. What is a shebang line?
  5. What does the shebang line look like for a Bourne shell script? A Bourne again shell script? Explain the differences.