- Table of Contents
- Copyright
- About the Author
- Acknowledgments
- Tell Us What You Think!
- Introduction
- Part I: Introduction to Mac OS X
- Chapter 1. Mac OS X Component Architecture
- Chapter 2. Installing Mac OS X
- Chapter 3. Mac OS X Basics
- Chapter 4. The Finder: Working with Files and Applications
- Chapter 5. Running Classic Mac OS Applications
- Part II: Inside Mac OS X
- Chapter 6. Native Utilities and Applications
- Chapter 7. Internet Communications
- Chapter 8. Installing Third-Party Applications
- Part III: User-Level OS X Configuration
- Chapter 9. Network Setup
- Chapter 10. Printer and Font Management
- Chapter 11. Additional System Components
- Part IV: Introduction to BSD Applications
- Chapter 12. Introducing the BSD Subsystem
- Chapter 13. Common Unix Shell Commands: File Operations
- Part V: Advanced Command-Line Concepts
- Chapter 14. Advanced Shell Concepts and Commands
- Chapter 15. Command-Line Applications and Application Suites
- Chapter 16. Command-Line Software Installation
- Chapter 17. Troubleshooting Software Installs, and Compiling and Debugging Manually
- Chapter 18. Advanced Unix Shell Use: Configuration and Programming (Shell Scripting)
- Customizing Your Shell Environment and Storing Data
- Automating Tasks with Shell Scripts
- Making Shell Scripts Start at Login or System Startup
- Summary
- Part VI: Server/Network Administration
- Chapter 19. X Window System Applications
- Chapter 20. Command-Line Configuration and Administration
- Chapter 21. AppleScript
- Chapter 22. Perl Scripting and SQL Connectivity
- Chapter 23. File and Resource Sharing with NetInfo
- Chapter 24. User Management and Machine Clustering
- Chapter 25. FTP Serving
- Chapter 26. Remote Access and Administration
- Chapter 27. Web Serving
- Part VII: Server Health
- Chapter 28. Web Programming
- Chapter 29. Creating a Mail Server
- Chapter 30. Accessing and Serving a Windows Network
- Chapter 31. Server Security and Advanced Network Configuration
- Chapter 32. System Maintenance
- Appendix A. Command-Line Reference
- Appendix B. Administration Reference
Automating Tasks with Shell Scripts
With as many times as we've mentioned how powerful shell scripting can be and how much time and effort it can save you, you might be expecting that writing shell scripts is going to require dealing with some additional level of complexity on top of what you've already learned. Shell scripts are simple programs that you write in the language of the shell, and if you've made it this far in the book, you've been learning and working in the language of the shell for a few chapters now. If you consider this fact, and the notion that Unix, by design, attempts to abstract the notion of input and output so that everything looks the same to the OS, you might have a good guess at what we'll say next: That's right—you already know how to write shell scripts. There are a few more shell techniques that you can learn to enhance your ability to program the shell, but Unix itself doesn't care whether it's you typing at a command prompt or commands being read out of a file on disk. Everything you've typed so far in working with the shell could have been put in a file, and the computer could have typed it to itself—voila, a shell script.
At its most trivial, a shell script can be exactly what you type at a prompt to accomplish some set of tasks. If you find that you have a need to repeatedly execute the same commands over and over, you can type them once into a file, make that file executable, and forever after execute them all just by typing the name of the file.
It really is as simple as it sounds, but just in case it's not quite clear yet, an example should help. Consider the following situation: Let's say that every day when you log in to your computer, you like to check the time (with date), check to see who's online (using the who command), check to see how much space is left on the drive with your home directory (with df), and finally check who's most recently sent you mail (with from).
You could type in each of these things to a command prompt when you log in to your machine, or you could put them in a file, make it executable, and let the file "type" them for you.
soyokaze ray 226> date
Mon Jun 18 23:35:55 EDT 2001
soyokaze ray 227> who
joray ttyp0 Jun 14 18:22 (140.254.12.151)
ray ttyp1 Jun 18 21:49 (24.95.74.211)
ray ttyp2 Jun 15 10:00 (rodan.chi.ohio-s)
radman ttyp3 Jun 18 23:33 (ac9d3e22.ipt.aol)
soyokaze ray 228> df .
Filesystem kbytes used avail capacity Mounted on
/dev/sd2g 953619 846078 12180 99% /priv
soyokaze ray 229> from | tail -10
From vanbrink@home.ffni.com Mon Jun 18 16:20:23 2001
From billp@abraxis.com Mon Jun 18 17:28:33 2001
From douglas_mille70@hotmail.com Mon Jun 18 18:34:28 2001
From owner-c-r-ffl@serge.shelfspace.com Mon Jun 18 19:23:42 2001
From owner-c-r-ffl@serge.shelfspace.com Mon Jun 18 20:42:53 2001
From owner-c-r-ffl@serge.shelfspace.com Mon Jun 18 21:24:00 2001
From buckshot@wcoil.com Mon Jun 18 22:02:15 2001
From jray@poisontooth.com Mon Jun 18 22:28:56 2001
From jray@poisontooth.com Mon Jun 18 23:15:28 2001
From owner-c-r-ffl@serge.shelfspace.com Mon Jun 18 23:34:43 2001
soyokaze ray 230> cat > imhere
#!/bin/csh
date
who
df .
from | tail -10
soyokaze ray 231> chmod 755 imhere
soyokaze ray 232> imhere
Mon Jun 18 23:36:51 EDT 2001
joray ttyp0 Jun 14 18:22 (140.254.12.151)
ray ttyp1 Jun 18 21:49 (24.95.74.211)
ray ttyp2 Jun 15 10:00 (rodan.chi.ohio-s)
Filesystem kbytes used avail capacity Mounted on
/dev/sd2g 953619 846078 12180 99% /priv
From billp@abraxis.com Mon Jun 18 17:28:33 2001
From douglas_mille70@hotmail.com Mon Jun 18 18:34:28 2001
From owner-c-r-ffl@serge.shelfspace.com Mon Jun 18 19:23:42 2001
From owner-c-r-ffl@serge.shelfspace.com Mon Jun 18 20:42:53 2001
From owner-c-r-ffl@serge.shelfspace.com Mon Jun 18 21:24:00 2001
From buckshot@wcoil.com Mon Jun 18 22:02:15 2001
From jray@poisontooth.com Mon Jun 18 22:28:56 2001
From jray@poisontooth.com Mon Jun 18 23:15:28 2001
From owner-c-r-ffl@serge.shelfspace.com Mon Jun 18 23:34:43 2001
From owner-c-r-ffl@serge.shelfspace.com Mon Jun 18 23:36:48 2001
As you can see, executing the file imhere, containing my commands, produces essentially the same output, with much less typing. (The output has a few changes because one user has left the system, and new mail has arrived between the by-hand runs and the execution of the shell script.)
The only part of the imhere script that might be confusing is the first line, #!/bin/csh. The shell interprets the first line of a shell script in a special manner. If a pattern such as this is found #! <path to an executable file> , the executable file named in that line is used as the shell for executing the contents of the script.
Single-Line Automation: Combining Commands on the Command Line
Before we go too far with the notion of storing collections of commands in files, however, let's look at what can be done at just the command-line level. You already know about using pipes and variables. These concepts can be combined to produce very powerful expressions directly at the command line, without any need to store the collection of commands in a file.
Consider for a moment the netpbm collection of graphics manipulation programs that was installed in Chapter 17, "Troubleshooting Software Installations, and Compiling and Debugging Manually." Included in the capabilities of the suite are a number of conversions among various file formats, as well as a range of manipulations of the image content itself. With Mac OS, if you want to convert a GIF file into a PICT file, and convert it to four-color grayscale along the way, you have a number of options. You could fire up PhotoShop or GraphicConverter, and perform the changes there, and save thefile. Alternatively, you could program a conversion filter in DeBabelizer to perform this manipulation for you. With netpbm, you can perform the manipulation from the command line:
soyokaze ray 277> ppmtogif < john.ppm > john.gif
ppmtogif: computing colormap...
ppmtogif: 192 colors found
soyokaze ray 278> giftopnm < john.gif > john.pnm
soyokaze ray 279> ppmtopgm < john.pnm > john.pgm
soyokaze ray 280> ppmquant 4 < john.pgm > john.pgm2
ppmquant: making histogram...
ppmquant: 120 colors found
ppmquant: choosing 4 colors...
ppmquant: mapping image to new colors...
soyokaze ray 281> ppmtopict < john.pgm2 > john.pict
ppmtopict: computing colormap...
ppmtopict: 4 colors found
Figure 18.1 shows a comparison of the original image john.gif, and the four-color grayscale image, john.pict.
Figure 18.1 A comparison of an original file and the result of processing it through one of a number of different netpbm filters.
From the brief discussion at the beginning of this section, you should already have an idea of how you could combine all that into a single file, if for some reason you wanted to perform that conversion to the john.gif file over and over and over.
This seems to be not very useful a thing to automate, and quite a bit of typing to boot (although, frankly, not nearly as much work as starting up Photoshop to do something this simple!). Let's see what we can do with pipes and shell variables to cut down on the amount of typing.
First, observe that all the programs are taking the input files on STDIN, and are producing output on STDOUT. Unix command-line programs are frequently like this, and it's a very good thing. Using the power of pipes to connect one program's STDOUT to another program's STDIN, we can shorten that collection of commands to a single command line:
soyokaze ray 287> giftopnm < john.gif | ppmtopgm | ppmquant 4 | ppmtopict > john.pict
ppmquant: making histogram...
ppmquant: 120 colors found
ppmquant: choosing 4 colors...
ppmquant: mapping image to new colors...
ppmtopict: computing colormap...
ppmtopict: 4 colors found
I'll let you verify that the output is graphically identical on a file of your own.
You might think that it's probably not very likely that you'll want to perform this single manipulation repeatedly to the same image. However, there are many times when you'd like to be able to perform a collection of manipulations like that on a number of different images. With what you know about shell variables, you might be able to come up with a way to abstract that command line so that it could be reused for any GIF file. You might try something like this:
soyokaze ray 288> set infile=john.gif
soyokaze ray 289> giftopnm < $infile | ppmtopgm | ppmquant 4 | ppmtopict > $infile:r.pict
ppmquant: making histogram...
ppmquant: 120 colors found
ppmquant: choosing 4 colors...
ppmquant: mapping image to new colors...
ppmtopict: computing colormap...
ppmtopict: 4 colors found
Now, you could simply use new values for $infile, and you'd have a reusable command that could perform the same manipulation on any GIF image.
It's still too much work though, right? Well, remember aliases? We can further automate things by using an alias command to compact that large command-line expression into something more manageable.
soyokaze ray 290> alias greyconvert 'set infile=\!#:* ; giftopnm < $infile | ppmtopgm |ppmquant 4 | ppmtopict > $infile:r.pict ' soyokaze ray 291> greyconvert john.gif ppmquant: making histogram... ppmquant: 120 colors found ppmquant: choosing 4 colors... ppmquant: mapping image to new colors... ppmtopict: computing colormap... ppmtopict: 4 colors found
That command is getting pretty long, isn't it? The good news is that for almost any task in Unix, you can figure out how to build up to an expression like this, just as shown here. Start by figuring out how to do it one step at a time on the command line, and work your way up to an elegant solution that solves the problem for you with as little repetitive work as necessary.
After you've invented useful aliases such as this one for yourself, remember to store them in your .cshrc or .tcshrc file in your home directory, or in your aliases.mine file in ~/Library/init/tcsh/, so that you can use them again, whenever you log in to your computer.
Multi-Line Automation: Programming at the Prompt
Creating customized commands that perform special functions like the greyconvert command built in the previous sections is useful, but it still doesn't address the need to automate tasks. For that, we need some sort of looping command, and the tcsh shell offers two: the foreach command and the while command. Both commands repeat a block of shell commands. The first executes it "for each" of its arguments, and the second executes it "while" some condition is true.
The foreach command and while command of the shell are unlike other shell commands that you've become familiar with in that they require additional information beyond the first command line. For example, the syntax for the foreach command is
foreach <variablename> ( <item list> ) <first command to execute> <second command to execute> . . . <nth command to execute> end
The while command, on the other hand, has the syntax
while ( <comparison> ) <first command to execute> <second command to execute> . . . <nth command to execute> end
In the foreach command, the <item list> can be a space-separated list of items, or a command that produces a space-separated (or return-separated) list of items, or a command-line wildcard that matches a list of files. As a demonstration, consider a situation in which we want to execute our previous greyconvert command on every GIF file in a directory containing many files. This can be accomplished in several ways by the use of the foreach command. The following example demonstrates the wildcard match to all the files of interest:
localhost amg 246% ls
AMG_cal-cover.gif AhMyGoddess-v05.gif AhMyGoddess-v10-f1.gif
AhMyGoddess-v01-f1.gif AhMyGoddess-v06-f1.gif AhMyGoddess-v10-i1.gif
AhMyGoddess-v01.gif AhMyGoddess-v06.gif AhMyGoddess-v10-i2.gif
AhMyGoddess-v02-f1.gif AhMyGoddess-v07-f1.gif AhMyGoddess-v10-i3.gif
AhMyGoddess-v02.gif AhMyGoddess-v07.gif AhMyGoddess-v10.gif
AhMyGoddess-v03-f1.gif AhMyGoddess-v08-f1.gif amg-nt0694_cover.gif
AhMyGoddess-v03.gif AhMyGoddess-v08.gif amg-nt0694_i1.gif
AhMyGoddess-v04-f1.gif AhMyGoddess-v09-f1.gif amg-nt0694_i2.gif
AhMyGoddess-v04.gif AhMyGoddess-v09.gif
AhMyGoddess-v05-f1.gif AhMyGoddess-v10-b.gif
localhost amg 247% foreach testfile ( *.gif )
foreach -> greyconvert $testfile
foreach -> end
ppmquant: making histogram...
ppmquant: 165 colors found
ppmquant: choosing 4 colors...
ppmquant: mapping image to new colors...
ppmtopict: computing colormap...
ppmtopict: 4 colors found
ppmquant: making histogram...
ppmquant: 159 colors found
ppmquant: choosing 4 colors...
ppmquant: mapping image to new colors...
ppmtopict: computing colormap...
ppmtopict: 4 colors found
.
.
.
ppmquant: making histogram...
ppmquant: 163 colors found
ppmquant: choosing 4 colors...
ppmquant: mapping image to new colors...
ppmtopict: computing colormap...
ppmtopict: 4 colors found
localhost amg 248% ls
AMG_cal-cover.gif AhMyGoddess-v05-f1.pict AhMyGoddess-v10-b.gif
AMG_cal-cover.pict AhMyGoddess-v05.gif AhMyGoddess-v10-b.pict
AhMyGoddess-v01-f1.gif AhMyGoddess-v05.pict AhMyGoddess-v10-f1.gif
AhMyGoddess-v01-f1.pict AhMyGoddess-v06-f1.gif AhMyGoddess-v10-f1.pict
AhMyGoddess-v01.gif AhMyGoddess-v06-f1.pict AhMyGoddess-v10-i1.gif
AhMyGoddess-v01.pict AhMyGoddess-v06.gif AhMyGoddess-v10-i1.pict
AhMyGoddess-v02-f1.gif AhMyGoddess-v06.pict AhMyGoddess-v10-i2.gif
AhMyGoddess-v02-f1.pict AhMyGoddess-v07-f1.gif AhMyGoddess-v10-i2.pict
AhMyGoddess-v02.gif AhMyGoddess-v07-f1.pict AhMyGoddess-v10-i3.gif
AhMyGoddess-v02.pict AhMyGoddess-v07.gif AhMyGoddess-v10-i3.pict
AhMyGoddess-v03-f1.gif AhMyGoddess-v07.pict AhMyGoddess-v10.gif
AhMyGoddess-v03-f1.pict AhMyGoddess-v08-f1.gif AhMyGoddess-v10.pict
AhMyGoddess-v03.gif AhMyGoddess-v08-f1.pict amg-nt0694_cover.gif
AhMyGoddess-v03.pict AhMyGoddess-v08.gif amg-nt0694_cover.pict
AhMyGoddess-v04-f1.gif AhMyGoddess-v08.pict amg-nt0694_i1.gif
AhMyGoddess-v04-f1.pict AhMyGoddess-v09-f1.gif amg-nt0694_i1.pict
AhMyGoddess-v04.gif AhMyGoddess-v09-f1.pict amg-nt0694_i2.gif
AhMyGoddess-v04.pict AhMyGoddess-v09.gif amg-nt0694_i2.pict
AhMyGoddess-v05-f1.gif AhMyGoddess-v09.pict
In this example, the foreach testfile ( *.gif ) line could have been replaced, with the following variants, with identical results:
foreach testfile ( `ls *.gif` ) foreach testfile ( AMG_cal-cover.gif AhMyGoddess-v01-f1.gif ... amg-nt0694_i2.gif )
The while command works similarly, executing its code block while some <condition> holds:
localhost ray 225> set x = 10
localhost ray 226> while ( $x > 0 )
while -> echo $x
while -> @ x = ( $x - 1 )
while -> end
10
9
8
7
6
5
4
3
2
1
localhost ray 227> echo $x
0
This example obviously doesn't have much day-to-day applicability. Most while expressions that are actually useful do things like watch for particular events to occur, such as the existence of temporary files, or disk space usage of more than or less than some value. None of these is particularly easy to demonstrate in a text-only format such as a book, but we expect that you'll get the idea fairly quickly. Table 18.7 shows the conditional operators that can be used to construct the <condition> part of while loops and if conditional statements.
Table 18.7. Logical, Arithmetical, and Comparison Operators Comparison Operators
| Operator or Symbol | Function |
| || | Boolean OR arguments. |
| && | Boolean AND arguments. |
| | | Bitwise Boolean OR. |
| ^ | Bitwise Exclusive OR. |
| & | Bitwise Boolean AND. |
| == | Equality comparison of arguments ($x == $y is true if the value in $x equals the value in $y). |
| Compares arguments as strings. | |
| != | Negated equality comparison of arguments ($x != $y is true if the value in $x is not equal to the value in $y). |
| Compares arguments as strings. | |
| =~ | Pattern-matching equality comparison (matches shell wildcards). |
| Compares arguments as strings. | |
| !~ | Pattern-matching negated equality comparison. |
| Compares arguments as strings. | |
| <= | Less than or equal to. |
| >= | Greater than or equal to. |
| < | Less than. |
| > | Greater than. |
| << |
Bitwise shift left. To avoid the shell interpreting this as redirection, it must be in a parenthesized subexpression. For example, set y = 32; @ x = ( $y << 2 ) |
| >> | Bitwise shift right. See preceding comment. |
| + | Add arguments. |
| - | Subtract arguments. |
| * | Multiply arguments. |
| / | Divide arguments. |
| % | Modulus operator (divide and report remainder). |
| ! | Negate argument. |
| ~ | Ones complement of argument. |
| ( | Open parenthesized subexpression for higher-order evaluation. |
| ) | Close parenthesized subexpression for higher-order evaluation. |
Another common use for the while command is to create infinite loops in the shell. This is a way to do things such as cause a shell command to execute over and over, potentially creating something like a "drop directory" that automatically processes files that are copied to it. For example, if we want to create a directory into which we could copy GIF files, and any files copied into it would have the greyconvert process run on it automatically, and then the GIF files would be deleted, we might try something like the following:
localhost Pictures 243> while (1) while -> foreach testfile (*.gif) while -> greyconvert $testfile while -> rm $testfile while -> end while -> sleep 60 while -> end
This while command attempts to loop perpetually (the value of 1 is true for the purposes of a comparison expression), and to internally execute our previous foreach loop to convert any files that match the *.gif pattern in the current directory. An rm command has been added to the foreach loop to remove the GIF file after it has been converted. The sleep 60 command after the foreach command's end causes the while loop to pause for 60 seconds before going on to its end statement and re-looping to the top of the while.
Unfortunately, this does not quite work properly, as when there are no files that match *.gif, the foreach line fails without creating its loop, and the end on line 4 is mistaken as intended to end the while loop.
Thankfully, you can work around this problem by applying the final topic in our discussion of shell scripts.
Storing Your Automation in Files: Proper Scripts
With all the power available to you directly at the command line, the move to putting shell scripts in files should seem almost anticlimactic in its lack of complexity. As mentioned at the beginning of this chapter, anything that you can type on the command line, you can put in a file, and the system will quite happily execute it for you, if you make the file executable. It really is that simple. There'd be little more to say, except that putting your script in a file allows you to conveniently separate parts of the execution into separate shells, preventing conflicts such as those just demonstrated earlier with the while and foreach loops.
Any script that is put in a file and directly executed (rather than source, in your current shell) creates its own shell in which to execute. To use this to make the previous example function properly, we can put the foreach section of the command into its own file:
localhost Pictures 244>cat > greyconv.csh
#!/bin/csh
foreach testfile (*.gif)
greyconvert $testfile
rm $testfile
end
localhost Pictures 245> chmod 755 greyconv.csh
localhost Pictures 246> ls -l greyconv.csh
-rwxr-xr-x 1 ray staff 75 Jun 23 01:58 greyconv.csh
localhost Pictures 247> cat greyconv.csh
#!/bin/csh
foreach testfile (*.gif)
greyconvert $testfile
rm $testfile
end
Then our perpetual while command can be run as
localhost Pictures 248> while (1)
while -> ./greyconv.csh
while -> sleep 60
while -> end
This command will loop perpetually in the current directory, executing the greyconv.csh shell script every 60 seconds. Any file with a .gif extension that is placed in the directory will be passed through the greyconvert alias that we created earlier, and then the original file will be deleted. This will run perpetually in the directory, allowing any file dropped in to be converted (within 60 seconds) automatically. Because the end of the foreach is in a completely separate shell, it can't accidentally end the while, and everything will work as expected. Of course, if there were a reason to do this on a regular basis, you could put that while loop into its own shell script file. It could be stored and executed as a single command from the command line just like any other command.
Two final things to note because we've now introduced independent shells invoked as the result of placing scripts in files: the $argv[1]...$argv[ n ] and corresponding $1...$ n command-line argument variables. (Refer to the table of shell variables and the table of alternative variable addressing methods for a refresher on these.) This also allows us to introduce the if ( <comparison> ) shell command, allowing conditional execution of code blocks.
If we'd like to make the greyconv.csh script somewhat more general, we can make use of the ability to pass arguments into the script. For example, it would be nice to be able to use greyconv.csh on the GIF files in a directory, without actually having to be in the directory when we run the while loop. This is easily accomplished by using the shell argument variables. The modified version of greyconv.csh is as follows:
#!/bin/csh if ( $?1 == 1 ) then cd $1 endif foreach testfile (*.gif) greyconvert $testfile rm $testfile end
This version of greyconv.csh demonstrates both an if conditional expression, and the use of a command-line argument. The if statement checks whether the variable $1 is set (using the $? <variablename> alternative variable addressing to check for existence). If it is set, the script assumes that the value in $1 is the name of a directory, and it cds into that directory before executing the foreach loop to convert and delete the GIF files. gre y conv.csh can now be called with a directory, to operate in that directory, or without a directory specified, to operate in the current directory.
We hope that gives you a few ideas for how powerful shell scripts can be, and how you can use them to make your use of OS X much more productive. We've only just scratched the surface in this chapter, and have trivialized some explanations to their simplest case to avoid a chapter that takes half the book. What's here can get you quite a way into shell scripting, and many Unix users with years of experience don't use more than a fraction of what's covered here. Still, if you're looking for more power and more capabilities, don't hesitate to go to the man pages and shell programming–specific reference books.
Making Shell Scripts Start at Login or System Startup | Next Section

Account Sign In
View your cart