Выбрать главу

With a Makefile like this, you no longer have to remember a multitude of commands and which ones should be used for which updated files. You never have to worry about forgetting to type a command. Many complicated procedures are reduced to:

Edit the appropriate file.

Type make.

make can be the ultimate tool for bringing together many smaller automated processes. Once, I had to merge the processes and procedures for three large networks into one. Each network had a different way of managing its aliases, hosts, and other administrative files. As I learned the procedures for each network, I constructed a Makefile specific to that network's master server. The high-level recipe names were the same in all three networks, but the commands they ran to accomplish the work were specific to each network.

The strategy was to create a new master server that would eventually replace all the legacy servers. Initially, the new master's Makefile simply initiated a make on the three legacy masters via rsh (this was long before ssh). I then migrated recipes to the new master one at a time. For example, first I decided that the new master would be the single source for the aliases file. I merged the aliases files of the three legacy networks and put the result on the new master. Once it was tested there, I added recipes on the new master to push that merged file to the legacy masters as if it were their own. I continued this process for each file or database.

Since each change was small and specific, I could test it incrementally. After literally hundreds of small changes, all the servers were "singing from the same songbook." At that point, it was easy to eliminate the legacy masters and let the new master be the authoritative master for all clients.

Warning

Any file that is automatically pushed to other servers should always have a comment at the top of the file warning other system administrators where the file came from and where to edit it.

Here's the warning I use: # THIS FILE IS MAINTAINED ON: server1.example.com # Edit it with this command: xed file.txt # If you edit this file on any other machine, # it will be overwritten. BE CAREFUL!

Since the previous note mentioned xed, I should explain what it is. There are many programs called xed, but this one can be found on http://www.nightcoder.com/code/xed. This program calls whatever editor you usually use ($EDITOR can be set to vi, pico, emacs, and so on) after locking the file. It is a must for any site that has multiple system administrators working on the same machine. If you are using RCS to track changes to a file, it does all the "check in" and "check out" work for you. This gives you infinite undo and a logfile of who changed what. If you find that a system has been acting funny for the last month, just check the log to see who changed the file a month ago and, well, be nice—we all make mistakes.

Hard Things Done Once

When we find ourselves doing something very difficult, automating the task records what we've done. When we do it in the future, it will be easier. This is how we build up our little bag of tricks.

Encapsulating a Difficult Command

Sometimes it takes hours to work out exactly the right command required to do something. For example, there is a program that creates ISO images, the kind you burn onto CD-ROMs. Its manual page describes hundreds of options, but to make an image readable by Windows, Unix, and Mac systems, the command is simply: $ mkisofs -D -l -J -r -L -f -P " Author Name " -V " disk label " -copyright copyright.txt -o disk.iso /directory/of/files

Sure, you can do it from a GUI, but where's the fun (or ability to script) in that?

This command also lets you do things not found in most GUIs, such as the ability to specify a copyright note, author name, and so on.

This is a good example of something to work into a .BAT file (DOS) or a Unix/Linux shell script.

Here's a shell script called makeimage1 that uses this: #!/bin/bash mkisofs -D -l -J -r -L -f -P "Limoncelli" -V 'date -u +%m%d' $*

The 'date -u +%m%d' sets the volume name to the current date.

One of the things that held me back from writing good scripts was that I didn't know how to process command-line parameters. Here are instructions for copying all the command-line arguments into a script.

The $* in the makeimage1 script means "any items on the command line." So, if you typed: $ makeimage1 cdrom/

then the $* would be replaced by cdrom/.

Since $* works for multiple arguments, you can also do: $ makeimage1 cdrom/ dir1/ dir2/

Then the $* would be replaced by all three components. In the case of mkisofs, this would merge all three directories into the CD-ROM image. You can refer to $1, $2, and so on, if you want to refer to specific items on the command line. In this example, $1 would refer to cdrom/, and $2 would refer to dir1/.

Another thing that prevented me from writing good scripts was not knowing how to process command-line flags like scriptname -q file1.txt. Thus, if a script I wanted to write was sophisticated enough to need command-line flags, I would use a different language or not write it at all. It turns out bash has a feature called getopt that does all the parsing for you, but the manual page for Bash isn't clear. It tells you how the getopt function works, but not how to use it. Finally, I found an example of how to use it and have been copying that example time and time again. It isn't important how it works; you don't even have to understand how it works or why it works to use it. You use it like this: args='getopt ab: $*' if [ $? != 0 ] then echo "Usage: command [-a] [-b file.txt] file1 file2 ..." exit -1 fi set -- $args for i do case "$i" in -a) FLAGA=1 shift ;; -b) ITEMB="$2" ; shift shift ;; --) shift; break ;; esac done

This would be a command that has flags -a and -b. -b is special because it must be followed by an argument such as -b file.txt. It you look at the first line, the getopt command is followed by the letters that can be flags. There is a colon after any letter that requires an additional argument. Later, we see a case statement for each possible argument, with code that either sets a flag or sets a flag and remembers the argument.