Project10.Write Shell Scripts


Project 10. Write Shell Scripts

In this project, you'll write a simple shell script using arguments, shell variables, and control constructs.

In Project 9, we learned that a shell script is a sequence of commands, written in a shell scripting language and stored in a file the shell can read, and we wrote a simple script of the kind runs a series of normal Unix commands, just as you would issue them on the command line, such as cd, ls, mv, and so on.

Learn More

This project covers the subject only briefly. Refer to the projects in Chapter 9 for shell script basics.


Now we'll take a look at a more advanced kind of shell script, one that takes advantage of features of the shell scripting language itself. These advanced features include variables and a variety of control constructs, and they enable techniques like loops and conditional processing. In combination with standard Unix commands, these scripting features allow the creation of sophisticated scripts that can adapt their behavior to different system conditions. A script might test for the presence of necessary support files before installing softwareand install any missing files before continuing.

We won't be that ambitious with our first conditional script; we'll just have it test to see whether a file exists, rename it if it does, and display an error message if it doesn't.

Learn More

The projects in Chapter 4 cover Unix text editors at greater length.


Once again, scripting requires the use of a text editor. If you're not familiar with any of the available Unix text editors, use Apple's TextEdit, but remember to save the file as plain text. In TextEdit, select menu Format and then item Make Plain Text. If you see the option Make Rich Text instead, your file is already in plain text.

Shell Arguments

Most Unix commands take arguments. In the command cp file1 file2, for example, cp is passed two arguments. Shell scripts, too, can take arguments, and this section shows how a script processes them. Looking at it from the inside (when writing a script as opposed to running it), arguments are often called parameters, so that's the term I'll use throughout this project.

Learn More

Refer to Project 9 if you are unsure of the basics of shell scripts.


Let's look at a ready-made script called rename that illustrates the basics. It doesn't exist on your Mac, so you're going to have to create it by typing it in a text editor and saving it. You can save it anywhereDocuments might be a good place to choose. This is how it looks displayed in cat.

$ cat rename #!/bin/bash # Usage: extension filename mv $2 $2.$1 $


Learn More

See Project 76 when you wish to learn more about parameter expansion in Bash.


This script expects to see two parameters: an extension and a filename. It adds the extension to the filename. A parameter is accessed using the notation $n, where n=1 for the first argument passed, 2 for the second, and so on. This is termed parameter expansion. The script is very primitive and doesn't do any more than command mv does, but we shall improve it over the course of the project. Let's give it execute permission and run it.

$ chmod +x rename $ ls f1      rename $ ./rename txt f1 $ ls f1.txt  rename $


The script does what it should but has a flaw: If it is passed a filename with spaces, it fails. In this example

$ ./rename txt "my file" usage: mv [-f | -i | -n] [-v] source target        mv [-f | -i | -n] [-v] source ... directory


the line

mv $2 $2.$1


expands to

mv my file my file.txt


To prevent this type of problem, it's prudent to enclose parameter expansion in double quotes. By doing so we preserve spaces in file names. Don't use single quotes, because single quotes stop parameters from being expanded.

Here's the new version of the script. Enclosing "$1" in quotes ensures that whatever $1 is expanded to, it will be treated as a single item, never as two space-separated items.

$ cat rename #!/bin/bash # Usage: extension filename mv "$2" "$2"."$1" $ ls my file         rename $ ./rename txt "my file" $ ls my file.txt     rename $


Learn More

See Project 80 to learn more about the black art of shell quoting.


Conditional Processing

Conditional processing says to do something only if particular conditions are met and possibly to do something else if they are not. One improvement to the rename script is to ensure that at least two arguments are passed.

$ cat rename #!/bin/bash # Usage: extension filename if [ $# -ne 2 ]; then   echo "Usage: $0: extension filename" else   mv "$2" "$2"."$1" fi $


An if construct evaluates the condition that follows in [ square brackets ]. If the condition turns out to be true, the statements between then and else are executed; if it turns out to be false, the statements between else and fi are executed. Any number of statements is allowed in either part of the if statement, even other if-then-else-fi statements. Notice that within the if and else parts, other statements are indented to make the structure of the script easier to read.

Note

The white space after the open bracket and before the close bracket is necessary, as is the semicolon. Get the syntax wrong, and Bash will complain. It is permissible to miss out the semicolon and put the then statement on a new line.


The special parameter expansion $# expands to the number of arguments passed, and within a condition, -ne means not equal to. So the condition is testing "if the number of arguments is not equal to 2." The special parameter expansion $0 on the next line expands to the name of the shell script itself, as typed on the command line.

Tip

To learn about the sorts of conditions that can be used, read the man page for [. Issue command help test.


The else part is optional, and this is perfectly legal.

if [ condition ]; then   statements fi


Running the improved rename with too few parameters gives

$ ./rename txt Usage: ./rename: extension filename $


Learn More

Project 81 covers Bash conditions in greater depth.


Multiple Conditions

An if statement may have multiple conditions, each additional condition being introduced by elif.

if [ "$1" = "positive" ]; then   echo "Yes" elif [ "$1" = "negative" ]; then   echo "No" else   echo "Not sure" fi


Alternatively, if all the conditions test for alternative values of a single variable, use a case statement. Each alternative should be terminated by ;;. Otherwise, processing falls through to the next alternative instead of going to the end of the case. The very last alternative should be *), which catches all other possibilities.

case "$1" in   "positive")     echo "Yes"   ;;   "negative")     echo "No"   ;;   *)     echo "Not sure"   ;; esac


Loops

The rename script would be actually useful if it could take an arbitrary number of filenames onto which the extension is added. Doing this requires some sort of loop that will process all the parameters. In shell speak, "all the parameters" is represented by the special parameter expansion $*. Bash provides several looping constructs, of which the for loop is the most appropriate here.

$ cat rename #!/bin/bash # Usage: extension filename if [ $# -lt 2 ]; then   echo "Usage: $0: extension filename" else   # loop to process each parameter   for file in $*; do     # rename the file     echo mv "$file" "$file"."$1"   done fi


Tip

A case alternative does not have be exact. It's possible to specify a pattern such as Y*|y*) to match Y, y, Yes, yep, and so on. See "Pattern Matching" in the Bash man page.


The for loop repeats for each value given in a list of values and assigns the next value to variable file each time around the loop. You may use any variable name here instead of file, of course. In this example, the list is $*, which expands to all parameters (all arguments passed to the script).

As a precaution, the line that does the actual moving is preceded by echo.

echo mv "$file" "$file"."$1"


The move command will not be executedjust displayed on the terminal. In this way, we can check that it will do as we expect, and if everything looks good, we remove echo.

Let's run the script.

$ ./rename txt "file 1" "file 2" mv txt txt.txt mv file file.txt mv 1 1.txt mv file file.txt mv 2 2.txt


D'oh! You will notice two problems, or bugs, with the script, First, we did not want to process the extension name within the loop; second, parameters with spaces are being split. A useful command called shift drops the first parameter off the list, which we can use after first saving the extension name in a shell variable. A shell variable is assigned with the syntax

variablename=value


and the value is recalled by

$variablename


To solve the second bug, we need only surround $* in double quotes, as we did with $1.

$ cat rename ... else   # save the extension then drop it from the parameter list   extension="$1"   shift   # loop to process each parameter   for file in "$*"; do     echo mv "$file" "$file"."$extension"   done fi $ ./rename txt "file 1" "file 2" mv file 1 file 2 file 1 file 2.txt


D'oh! Again! Quoting $* has created one big long parameter. Solving this problem would be tricky were it not for a neat feature of Bash. Enter "$@", which expands to "$1" "$2"... instead of "$1 $2...", which is what "$*" expands to.

$ cat rename ...   # loop to process each parameter   for file in "$@"; do     if [ -r "$file" ]; then       mv "$file" "$file"."$extension"     else       echo "No such file: $file"     fi   done fi


Now let's run the script.

$ ls f2      file 1      file 3      rename $ ./rename txt file\ 1 f2 rubbish "file 3" No such file: rubbish $ ls f2.txt file 1.txt file 3.txt rename


That looks better. Notice that an extra if condition has been added to check that the file we are trying to rename does exist and is readable. Check out the man page for test for a list of possible conditions.

Don't Forget the Quotes

A condition such as

if [ $1 = Yes ]


will cause a shell script error if $1 is empty. The condition will become

if [ = Yes ]


Use quotes so that the condition is always syntactically valid.

if [ "$1" = "Yes" ]


becomes

if [ "" = "Yes" ]



A while loop loops continually while a specified condition is true. The condition is formed in exactly the same way as for an if statement.

#!/bin/bash read -p "Give a filename: " fn while [ "$fn" != "" ]; do   if [ -e "$fn" ]; then     file "$fn"   else     echo "File $fn does not exist"   fi   read -p "Give a filename: " fn done


This script prompts you to enter a filename, then displays the content type of the file you named (if it exists). It accepts filenames that contain spaces, without requiring that they be quoted.

Tip

Learn more about a Bash built-in command by using the command

$ help nameofcommand



Bash also provides an until loop, which simply uses the keyword until instead of while. As you might guess from the linguistic sense of the construct, the condition for exiting the loop is reversed.




Mac OS X UNIX 101 Byte-Sized Projects
Mac OS X Unix 101 Byte-Sized Projects
ISBN: 0321374118
EAN: 2147483647
Year: 2003
Pages: 153
Authors: Adrian Mayo

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