Creating Your Own Commands Using Shell Scripting


Now that you've acquired a sizable amount of experience using the console and the shell; have learned a number of common commands; and are familiar with concepts such as pipes, command substitution, variable substitution, and quoting, it's time to begin creating shell scripts.

A shell script is really a way of automating complex tasks or repetitive sequences of commands that you often carry out at the shell so that you don't have to type these commands over and over each time you want to repeat the task. Often, the sequences of commands in shell scripts are complex enough that your shell scripts really become new commands, tailor-made to your productivity needs.

Structurally, a shell script is a plaintext file that contains normal commands just as you might type them at the command line, in the order in which you want them to be executed when the script is run. In more complex shell scripts, you might also find various special control statements that are interpreted and acted upon by the shell. They can cause the shell to call extra commands or to ignore some commands in the script, depending on user input or testable environmental conditionssuch as the existence (or nonexistence) of a particular file or directory.

Beginning a Shell Script

To create a shell script, launch your favorite editor (generally vi or emacs) and create a new file called myscript. On the first line of this file, enter the following:

 #!/bin/sh 

This line indicates to Linux that the file you are creating is a shell script. When you call myscript from the command line, Linux checks the first line of the file, realizes that it is a shell script, and then calls the shell, /bin/sh, to execute your list of commands.

Processing Command-Line Arguments

The simplest use of shell scripting is to automate a series of commands you often use, to avoid always having to type them.

Recall that in Chapter 21, "Managing High-Quality Documents at the Command Line," you learned that printing a LaTeX file in Linux is a two-step process. First, you must run your input file through the formatter with the latex command to produce a dvi file. Then you must use dvips to print the file. You can use shell scripting to create a command that automates this process. The goal for our first script is to create a command that enables the user to pass one argumentthe name of a .tex fileand have the file both formatted and printed without needing to enter further commands.

You create this command using a special set of variables that hold command-line arguments. Within a shell script, the shell creates a special set of variables called 1, 2, 3, and so on to hold the command-line arguments entered by the user when the shell script was called. Using $1, $2, $3, and so on therefore substitutes the value(s) of the argument(s) the user provided.

Consider the script in Listing 25.1. This script creates a variable named LATEXFILE and assigns to it the value of the special variable $1. That means that the first argument passed to myscript will end up in the variable LATEXFILE as the script is running.

Why Put Quotation Marks Around Filenames?

Notice that nearly everything in myscript is enclosed in double quotation marks. In Linux, filenames may contain spaces. To access filenames that contain spaces at the command line, the filename must be quoted. By quoting every place where a file is accessed in myscript, you guarantee that the script will work if it is ever called with a filename containing one or more spaces as an argument.


Listing 25.1. A Shell Script Called myscript
 #!/bin/sh LATEXFILE="$1" DVIFILE="$(echo "$LATEXFILE" | sed 's/tex$/dvi/')" latex "$LATEXFILE" dvips "$DVIFILE" 

The second variable assignment in myscript is considerably more involved than the first, but it is manageable. The entire value of the variable is enclosed in $(), meaning that the value of DVIFILE will be obtained through command substitution; in other words, the output of the commands inside the parentheses becomes the value of DVIFILE.

Inside the parentheses, two things occur. First, the echo command echoes the value of LATEXFILE. Remember that this value is the name of a .tex file that was passed to your script by the user. The vertical bar then pipes the output of echo to the sed command, which replaces .tex with .dvi. Thus, if the value of LATEXFILE is myfile.tex, the assigned value of DVIFILE is myfile.dvi. If you want to see this at work outside a script, try typing it at the command line:

 [you@workstation20 you]$ echo myfile.tex | sed 's/tex$/dvi/' myfile.dvi [you@workstation20 you]$ 

The rest of the script in Listing 25.1 is simply a list of commands the shell is to execute. The script calls LaTeX with the name of the .tex file supplied by the user. You already know that when LaTeX formats this .tex file, the result is a .dvi file by the same name in the same directory as the original file.

Because a .dvi file can be printed with the dvips command, your script then performs this second step, calling dvips and supplying as an argument the .dvi file by the same name. This command prints the file.

Making myscript Easily Executable

After saving myscript, you have a file called myscript in /home/you, which contains all the commands necessary to allow the shell to automate the printing of .tex files for you. Naturally, you should try out the script to see whether it works. However, you can't do that just yet.

Recall that the shell maintains an environment variable called PATH that contains the list of directories in which commands are found. Before this script can be used like other commands, you must move it into one of the directories listed in the PATH variable so that the shell can find it and then give it executable permissions so that the shell can execute it. Because /home/you/bin appears in the PATH environment variable, you can prepare the script for execution by making sure that /home/you/bin exists (using mkdir to create it if it doesn't) and then using mv to move the script there and chmod to set executable permissions:

 [you@workstation20 you]$ mkdir ~/bin [you@workstation20 you]$ mv myscript ~/bin [you@workstation20 you]$ chmod u+x ~/bin/myscript [you@workstation20 you]$ ls -l ~/bin total 4 -rwxrw-r--  1 you   you      115 Oct 1 13:52 myscript [you@workstation20 you]$ 

Now try it out:

 [you@workstation20 you]$ myscript myfile.tex [...loads of output...] 

If you tried this experiment on a real LaTeX file, you should have enjoyed success; the file was formatted and printed. You have created a new command! The name myscript is a little generic, so rename it:

 [you@workstation20 you]$ mv ~/bin/myscript ~/bin/lxprint [you@workstation20 you]$ 

The new command myscript has now been renamed lxprint, and its calling syntax is as follows:

 lxprint filename.tex 

Here, filename.tex represents the name of the .tex file that should be printed.

Using Conditional Statements

Sometimes in a shell script it is helpful to execute part of the script if some condition is true or a variable holds some specific value. Perhaps you want a particular script to generate printed output only if the user calls the script with the -print option, or perhaps you want a script to create a new file only if a file by the same name doesn't already exist. This technique of omitting or including certain parts or behaviors of a script depending on current circumstances or user input is called conditional execution.

The most basic form of conditional execution in shell scripts is used with the following syntax. Here, the first set of commands is executed only if test expression is true. The optional word else precedes a second set of commands that will be executed only if test expression is not true:

 if [ test expression ]; then command command   ... else command command   ... fi 

The term test expression refers to one of a number of different kinds of tests and comparisons that can be performed. The most common of these expressions are shown in Table 25.1; many more can be found in the manual page for test.

Table 25.1. Common Test Expressions

Expression

Meaning

val1 = val2

True if the word or quoted text item val1 is identical to the word or quoted text item val2

val1 != val2

True if the word or quoted text item val1 is not identical to the word or quoted text item val2

val1 -gt val2

True if the numeric value of item val1 is greater than the numeric value of item val2

val1 -lt val2

True if the numeric value of item val1 is less than the numeric value of item val2

val1 -eq val2

True if the numeric value of item val1 and the numeric value of item val2 are equal

-e file

True if file is the name of an existing file of any kind

-f file

True if file is the name of an existing regular file (not a device or a directory)

-d directory

True if directory is the name of an existing directory

-x command

True if command is the name of an existing file that is marked as executable


To illustrate conditional execution, let's modify lxprint in two ways. First, let's add a few lines so that if the user supplies the classic --help as an option, lxprint prints a help message. Then let's force lxprint to verify that a file actually exists before it tries to call latex or dvips. If the file doesn't exist, lxprint should display an error message.

The new and improved lxprint is shown in Listing 25.2.

Listing 25.2. The New and Improved lxprint
  1:    #!/bin/sh  2:  3:    if [ "$1" = --help ]; then  4:        echo "Use lxprint to print out LaTeX files in one easy step."  5:        echo "Just supply the name of a .tex file as an argument!"  6:        exit 0  7:    fi  8:  9:    LATEXFILE="$1" 10: 11:    if [ -f "$LATEXFILE" ]; then 12:        DVIFILE="$(echo "$LATEXFILE" | sed 's/tex$/dvi/')" 13: 14:        latex "$LATEXFILE" 15:        dvips "$DVIFILE" 16:    else 17:        echo "There doesn't seem to be a file called $LATEXFILE." 18:        exit 1 19:    fi 

This listing includes two conditional statements. The first one, which begins on line 3 and ends on line 7, checks to see whether the argument supplied by the user is actually the word --help. If it is, a help message is displayed. The second conditional statement, which begins on line 11 and continues through line 19, checks for the existence of the file the user has asked to be formatted and printed. If the file exists, formatting and printing commence. If the file does not exist, an error message is printed.

Why Are Some Lines Indented?

Indenting four spaces for each new level of nesting in a script is traditional. Indenting commands within if...then conditional statements is not mandatory. However, in complex scripts in which if...then statements can occur within other if...then statements, indentation can make it much easier to visually understand how a script works.


Notice that one final command has also been introduced here. The exit command can be used to exit a shell script and return to the command prompt. It accepts one argument, the exit status, which should be zero if the script has exited normally or nonzero if the script is exiting because it has encountered a problem.

Let's try the two new conditional chunks of the script, first by calling lxprint with the argument --help and then by calling it with a nonexisting filename:

 [you@workstation20 you]$ lxprint --help Use lxprint to print out LaTeX files in one easy step. Just supply the name of a .tex file as an argument! [you@workstation20 you]$ lxprint nosuchfile.tex There doesn't seem to be a file called nosuchfile.tex. [you@workstation20 you]$ 

The changes are functional, and the lxprint command has been further improved.

Testing Over and Over Again

The types of tests shown in Table 25.1 aren't only for use with if...then statements. They can also be used with another type of statement, known as the while...do statement, which follows this format:

 while [ test expression ]; do command command   ... done 

The difference between if...then and while...do is that in the former case, commands are executed a single time if the test condition is true, and in the latter case, they are executed again and again and again as fast as the computer can execute them until the condition is false.

Let's continue to use lxprint as our test case. When you're working with publishing projects involving multiple documents, having multiple source files isn't uncommon. For example, a large project called 02projections might be split across several files ending in sequence numbers:

 02projections1.tex 02projections2.tex 02projectison3.tex 

It would be nice if lxprint could accept a base name like 02projections and then be smart enough to print all the files in the set in sequence. Using a while loop and some more conditional statements, you can do this easily. The result is shown in Listing 25.3. This script includes commentslines that begin with a hash mark (#)and are inserted for the benefit of humans only. The shell ignores comments.

Listing 25.3. The New lxprint Command Incorporating Sequence Printing
  1:    #!/bin/sh  2:  3:    # If the user uses '--help' as an argument,  4:    # we want to display a help message.  5:  6:    if [ "$1" = --help ]; then  7:        echo "Use lxprint to print out LaTeX files in one easy step."  8:        echo "Just supply the name of a .tex file as an argument!"  9:        echo " * or the base name of a numbered sequence of .tex files" 10:        echo "  to print them all!" 11:        exit 0 12:    fi 13: 14:    LATEXFILE="$1" 15: 16:    # If the filename supplied as an argument exists, 17:    # then print it out. 18:    if [ -f "$LATEXFILE" ]; then 19:        DVIFILE="$(echo "$LATEXFILE" | sed 's/tex$/dvi/')" 20: 21:        latex "$LATEXFILE" 22:        dvips "$DVIFILE" 23:    else 24:  25:        # If the filename doesn't exist as supplied, see if 26:        # it's a base filename-- for example, if the user 27:        # supplied '02projections' as an argument and the file 28:        # '02projections1.tex' exists, then the user wants to 29:        # print the entire sequence. 30: 31:      if [ -f "${LATEXFILE}1.tex" ]; then 32:          # The variable COUNTER will hold the current number 33:          # in the sequence we're printing. We'll start at 34:          # 1 and use expr to add one to it each time we 35:          # print another file. 36: 37:          COUNTER=1 38:  39:          while [ -f "$LATEXFILE$COUNTER.tex" ]; do 40:            echo "Printing $LATESFILE$COUNTER.tex" 41:            latex "$LATEXFILE$COUNTER.tex" 42:            dvips "$LATEXFILE$COUNTER.dvi" 43:            COUNTER=$(expr $COUNTER + 1) 44:          done 45: 46:        else 47: 48:          echo "There doesn't seem to be a file called $LATEXFILE." 49:          exit 1 50: 51:        fi 52:    fi 

The execution of lxprint is gradually getting more complex. Now when lxprint starts, it first checks to see whether the user has sent --help as an argument in lines

612. If this isn't the case, it checks to see whether the user has supplied a valid filename in line 18; if the user has, the file is formatted and printed in lines 1922. If the argument isn't a valid filename, lxprint checks to see whether it might be a base filename by adding a number and the .tex extension to it in line 31. If it is, lxprint formats and prints all the files in the set, in sequence, by using a while..do loop and the COUNTER variable, which holds the number of times the commands in the loop have been executed so far; this happens in lines 3744. As long as another file in the sequence seems to exist, the loop is executed again and the value of COUNTER increases by 1.

Take a moment to study the script, the use of expr, and command and variable substitution as they appear here. Because of the size and complexity of the script, it might seem daunting at first, but you will eventually be able to follow the flow of the script as it is written.

Repeatedly Executing for a Predefined Set

Although the if..then and while..do statements both work by performing a test and making a decision based on the result, another statement, the for..do statement, simply executes a set of commands for every element in a list. For example, the for..do loop provides ideal functionality for performing the same set of operations on each file in a long list of files. The syntax is

 for var in item1 item2 item3 ...; do command command   ... done 

The for..do statement first assigns the value item1 to the shell variable var and then executes the commands inside the statement. After it reaches done, it assigns the value item2 to the shell variable var and executes the commands inside the statement again. This process continues until there are no more items.

Let's examine the use of the for..do loop by giving the lxprint command one more capabilityto print every .tex file in a user's current working directory if the user supplies --all as an argument or option. You can easily do this with a well-placed for..do statement. The updated and final lxprint is shown in Listing 25.4.

Listing 25.4. The lxprint Command with the --all Capability Added
  1:    #! /bin/sh  2:  3:    # If the user uses '--help' as an argument,  4:    # we want to display a help message.  5:  6:    if [ "$1" = --help ]; then  7:        echo "Use lxprint to print out LaTeX files in one easy step."  8:        echo "Just supply the name of a .tex file as an argument!"  9:        echo " * or the base name of a numbered sequence of .tex files" 10:        echo "  to print them all!" 11:        echo " * or --all to print every .tex file in the pwd!" 12:        exit 0 13:    fi 14: 15:    # If the user supplies '--all' as an argument, then use 16:    # filename expansion and a for..do statement to print 17:    # every .tex file in the present working directory, one 18:    # by one. Then exit. 19: 20:    if [ "$1" = --all ]; then 21:        for LATEXFILE in *.tex; do 22:          echo "Printing $LATEXFILE" 23:          DVIFILE="$(echo "$LATEXFILE" | sed 's/tex$/dvi/')" 24: 25:          latex "$LATEXFILE" 26:          dvips "$DVIFILE" 27:        done 28:        exit 0 29:    fi 30:  31:    LATEXFILE="$1" 32: 33:    # If the filename supplied as an argument exists, 34:    # then print it out.  35:    if [ -f "$LATEXFILE" ]; then 36:        DVIFILE="$(echo "$LATEXFILE" | sed 's/tex$/dvi/')" 37: 38:        latex "$LATEXFILE" 39:        dvips "$DVIFILE" 40:    else 41:  42:        # If the filename doesn't exist as supplied, see if 43:        # it's a base filename -- for example, if the user 44:        # supplied '02projections' as an argument and the file 45:        # '02projections1.tex' exists, then the user wants to 46:        # print the entire sequence. 47: 48:        if [ -f "${LATEXFILE}1.tex" ]; then 49: 50:           # The variable COUNTER will hold the current number 51:           # in the sequence we're printing. We'll start at 52:           # 1 and use expr to add one to it each time we 53:           # print another file. 54: 55:           COUNTER=1 56: 57:           while [ -f "$LATEXFILE$COUNTER.tex" ]; do 58:               echo "Printing $LATEXFILE$COUNTER.tex" 59:               latex "$LATEXFILE$COUNTER.tex" 60:               dvips "$LATEXFILE$COUNTER.dvi" 61:               COUNTER=$(expr $COUNTER + 1) 62:           done 63: 64:      else 65:  66:           echo "There doesn't seem to be a file called $LATEXFILE." 67:           exit 1 68: 69:      fi 70:    fi 

The change in Listing 25.4 is relatively small, but potentially confusing because *.tex looks like a single item. Remember, though, that the shell uses the * if it is not enclosed in quotation marks to expand to a list of matching filenames. So, in this case, *.tex is replaced by a space-separated list of .tex files, as if you had typed them yourself. The for..do statement then uses this list as the basis for the value of the LATEXFILE variable as it executes the commands between the do and the done in lines 2127. At the end of the for..do statement, exit in line 28 returns the user to the command prompt.

Going Beyond Shell Scripting

Believe it or not, all the techniques we've documented so far in this chapter can be used directly at the command line as well as from within shell script files. Although if..then has limited utility when entered directly at the command line, while..do and especially for..do can have incredible utility on a day-to-day basis.

To use one of these structures at the command line, enter it as you would in a script. After you begin a loop or conditional statement, the shell prompt changes and remains altered in appearance until you reach the closing fi or done; then the commands in the loop are executed. For example, you can use a similar for..do loop to the one in Listing 25.4 to print all the .txt files in your home directory to the printer. Just use the *.txt pattern as your for..do list and the lpr command inside the loop:

 [you@workstation20 you]$ for GONNAPRINT in *.txt; do > lpr "$GONNAPRINT" > done [you@workstation20 you]$ 

Notice how the prompt changed after you entered the for..do line and remained different until you entered the word done. After you enter done, the shell sends every file ending with .txt in the current working directory to the printer with the lpr command. Over time, you'll find unexpected uses for for..do and, to a lesser extent, while..do and if..then everywhere you looksometimes when you least expect them.

Shell scripting is a very powerful application development tool; the shell offers several techniques, statements, structures, commands, and facilities that are not discussed in this book. For more in-depth information on shell scripting and shell programming, I recommend the following books, which cover shell scripting and the bash shell commonly used with Linux in detail:

  • Sams Teach Yourself Shell Programming in 24 Hours by Sriranga Veeraraghaven

  • Linux Shell Scripting with Bash by Ken O. Burtch

  • Wicked Cool Shell Scripts by Dave Taylor

  • Learning the bash Shell by Cameron Newham and Bill Rosenblatt



    SAMS Teach Yourself Red Hat(r) Fedora(tm) 4 Linux(r) All in One
    Cisco ASA and PIX Firewall Handbook
    ISBN: N/A
    EAN: 2147483647
    Year: 2006
    Pages: 311
    Authors: David Hucaby

    flylib.com © 2008-2017.
    If you may any questions please contact us: flylib@qtcs.net