For example, the following entry limits use of the lprm command to users' own jobs and requires that the command be run on the same host from which the print job was submitted: ACCEPT SE
Trang 1This will allow only jobs in class check to print; all others will be held To allow any job to print, use this command:
# lpc class laser off
Using classes can be a bit tricky For example, if you alternate between printing checks and regular output on a printer, you probably don't want to turn off class
check after all the checks are printed Rather, you want check jobs to be held until the proper paper is again in place in the printer In this case, the following
command will be more effective:
# lpc class laser A
This sets the allowed class to class A (the default), so jobs spooled in class check will be held as desired.
13.6.2 Configuring LPRng
LPRng uses three configuration files (stored in /etc ): printcap , lpd.conf , and lpd.perms , which hold queue configuration, global spooler configuration and printer
access rules, respectively The first of these is a modified version of the standard LPD printcap file It uses a relaxed syntax: all fields use an equal sign to assign
values rather than having datatype-specific assignment characters (although the form name@ is still used to turn off Boolean flags), multiple line entries do not
require final backslash characters, and no terminal colon is needed to designate field boundaries.
Here are two simple entries from printcap :
hp: Example local printer.
:tc=.common Include the common section.
laser: Example remote printer.
:lp=painters@matisse
:tc=.common Include the common section.
.common: Named group of items.
:sd=/var/spool/lpd/%P
:mx=0
The first entry is for a local printer named hp on the first parallel port This printcap entry specifies a description for the printer, the name of its log and
accounting files, and a filter with which to process jobs The final field, tc , provides an "include" feature within printcap entries It takes a list of names as its argument In this case, the field says to include the settings in the printcap entry called common within the current entry Thus, it has the effect of removing any length limits on print jobs to printer hp and of specifying its spool directory as /var/spool/lpd/hp
The second printcap entry creates a queue for a remote printer, matisse on host painters , which also has no job length limits and uses the spool directory
/var/spool/lpd/laser The last two items are again set using the tc include field.
The LPRng printcap file allows for variable expansion within printcap entries We saw an example of this in the sd field in the preceding example The following
variables are supported:
Trang 213.6.2.1 Separate client and server entries
By default, printcap entries apply both to spooling system clients—user programs like lpr —and servers—the lpd daemon However, you can specify that an entry apply only to one of these contexts, as in these example entries:
laser:server Entry applies to the lpd daemon.
painters ) Clients will be able to send jobs directly to this remote printer.
In this next example, clients are required to use the local print daemon in order to print to the printer laser2 :
laser2:force_localhost Force clients to use the local server.
laser2:server
:lp=/dev/lp0
:sd=/var/spool/lpd/%P
The force_localhost setting (a Boolean, which is off by default) tells clients accessing this printcap entry to funnel jobs through the local lpd server process.
13.6.2.2 Using a common printcap file for many hosts
One of LPRng's most powerful capabilities is the built-in features for constructing a single central printcap file which can be copied to or shared among many
hosts This flexibility comes from the on setting (for "on host") Here is an example:
These entries define the printers color , laser , and draft on every host in ahania.com except astarte as the corresponding queue on astarte (which are defined
elsewhere in the printcap file).
Trang 3:filter=path and arguments
:bq_format=l Binary jobs will be sent on.
Here, the queue scribes sends jobs to queues lp1 , lp2 , and lp3 (which must be defined elsewhere in the printcap file), as each queue becomes free (which, of
course, occurs when the associated device is free) This mechanism provides a very simple form of load balancing.
Here is part of the printcap entry for lp1 :
lp1:
:sd=/var/spool/lpd/%P
:ss=scribes
The ss setting specifies the controlling queue for this printer Note that it does not prevent jobs from being sent directly to queue lp1 ; the only effect of this
setting seems to be to make queue status listings more readable.
Print job destinations can also be determined on a dynamic basis Here is an example:
smart:
:sd=/var/spool/lpd/%P
:destinations=matisse@printers,degas@france,laser
:router=/usr/local/sbin/pick_printer
The program specified in the router setting is responsible for determining the destination for each submitted print job The router program is a standard print
filter program Its exit status determines what happens to the job (0 means print, 37 means hold, and any other value says to delete the job), and it must write the queue destination and other information to standard output (where lpd obtains it) See the LPRng-HOWTO document for full details on dynamic print job
routing.
13.6.2.4 Filters
As we've noted before, print jobs are processed by filter programs before they are sent to the printer device Filters are responsible for initializing the device to a known initial state, transforming the output into a form that it understood by the printer, and ensuring that all output has been sent to the printer at the end of the job The first and third tasks are typically accomplished by adding internal printer commands to the beginning and end of the print job Filter programs are also responsible for creating printer accounting records.
As the examples we've looked at have shown, LPRng provides the filter printcap setting for specifying a default filter for all jobs in a particular queue In addition,
it supports many of the various output type-specific filter variables used in traditional printcap entries (i.e., the *f settings).
The LPRng package often uses the ifhp filter program also written by Patrick Powell It is suitable for use with a wide variety of current printers The
characteristics of the various supported printers are stored in its configuration file, ifhp.conf (usually stored in /etc ) The following printcap entry illustrates
Trang 4:sd=/var/spool/lpd/%P
:filter=/usr/local/libexec/filters/ifhp
:ifhp=model=default
The filter setting specifies the path to ifhp , and the ifhp setting specifies the appropriate printer definition with its configuration file In this case, we are using
the default settings, which work well with a wide variety of printers.
NOTE
The LPRng facility includes an excellent Perl script that demonstrates the method for getting page count information from modern printers It is called
accounting.pl and is included with the source distribution.
13.6.2.5 Other printcap entry options
It is also possible to store printcap entries in forms other than a flat text file For example, they could be stored in an LDAP directory LPRng allows for such
possibilities by allowing printcap entries to be fetched or created dynamically as needed This is accomplished by setting the printcap_path in the lpd.conf
configuration file as a pipe to a program rather than a path to a printcap file:
printcap_path=|program
Such an entry causes LPRng to execute the specified program whenever it needs a printcap entry (the desired entry is passed to the program as its standard
input) For example, such a program could retrieve printcap information from an LDAP directory See Chapter 11 of Network Printing for details and extended
examples.
13.6.3 Global Print Spooler Settings
The lpd.conf configuration file holds a variety of settings relating to the print spooler service Among the most important are ones related to printer connection
and timeouts and to print job logging Some of the most commonly used are listed in the example configuration file below:
# communication-related settings
connect_grace=3 Wait period between jobs (default=0).
network_connect_grace=3
connect_timeout=600 Cancel job after this interval (default=0).
send_try=2 Maximum number of retries (default is no limit).
max_servers_active=10 Max # lpd child processes (default is half the
system process limit).
max_status_line=80 Truncate entries to this length (default=no limit).
# central logging server
logger_destination=scribe Destination for log file entries.
logger_pathname=/tmp/lprng.tmp Local temporary file to use.
logger_max_size=1024 Max size of the temporary file (default=no limit).
logger_timeout=600 Wait time between connections to the remote
server (default is whenever data is generated).
13.6.4 Printer Access Control
The third LPRng configuration file, lpd.perms , is used to control access to the print service and its printers Each entry in the file provides a set of characteristics
against which potential print jobs are matched and also indicates whether such jobs should be accepted The first entry that applies to a specific print job will be used to determine its access Accordingly, the order of entries within the file is important.
The syntax of the lpd.perms file is explained best by examining some examples For example, these entries allow users to remove their own print jobs and root to
Trang 5ACCEPT SERVICE=M SERVER REMOTEUSER=root
REJECT SERVICE=M
The first keyword in an entry is always ACCEPT or REJECT , indicating whether matching requests are to be performed These entries all apply to the M service,
which corresponds to removing jobs with lprm The various entries allow the command to succeed if the user executing and the user owning the print jobs are
the same (SAMEUSER ), or if the user executing it is root (REMOTEUSER=root ) on the local system (SERVER ) All other lprm requests are rejected.
Available SERVICE codes include C (control jobs with lpc ), R (spool jobs with lpr ), M (remove jobs with lprm ), Q (get status info with lpq ), X (make
connection to lpd ), and P (general printing) More than one code letter can be specified to SERVICE
There are several keywords that are compared against the characteristics of the print job and the command execution context:
USER , GROUP , HOST , PRINTER
These items are compared to the ownership and other characteristics of the print job to which the desired command will be applied In addition, the
SERVER keyword requires that the command be executed on the local server.
REMOTEUSER , REMOTEGROUP , REMOTEHOST
These items are compared to the user, group, and host where or with whom the desired command originated Note that the "remote" part of the name can be misleading, because it need not refer to a remote user or host at all.
The preceding keywords all take a string or list of strings as their arguments These items are interpreted as patterns to be compared to the print job or command characteristics.
SAMEUSER , SAMEHOST
These keywords require that USER be the same as REMOTEUSER and HOST be the same as REMOTEHOST , respectively For example, the following
entry limits use of the lprm command to users' own jobs and requires that the command be run on the same host from which the print job was submitted:
ACCEPT SERVICE=M SAMEUSER SAMEHOST
We'll now examine some additional lpd.perms entries The following entry rejects all connections to the lpd server that originate outside the ahania.com domain
or from the hosts dalton and hamlet :
REJECT SERVICE=X NOT REMOTEHOST=*.ahania.com
REJECT SERVICE=X REMOTEHOST=dalton,hamlet
Note that these entries could not be formulated as ACCEPT s Hosts may be specified by hostname or by IP address.
The following entries allow only members of the group padmin to use the lpc command on the local host:
ACCEPT SERVICE=C SERVER REMOTEGROUP=padmin
REJECT SERVICE=C
The LPC keyword can be used to limit the lpc subcommands that can be executed For example, the following entry allows members of group printop to hold and
release individual print jobs and move them around within a queue:
ACCEPT SERVICE=C SERVER REMOTEGROUP=printop LPC=topq,hold,release
The following entries prevent anyone from printing to the printer test except user chavez :
ACCEPT SERVICE=R,M,C REMOTEUSER=chavez PRINTER=test
REJECT SERVICE=* PRINTER=test
User chavez can also remove jobs from the queue and use lpc to control it.
The following command prevents print job forwarding on the local server:
REJECT SERVICE=R,C,M FORWARD
The DEFAULT keyword is used to specify a default action for all requests not matching any other configuration file entry:
# All everything that is not explicitly forbidden.
DEFAULT ACCEPT
The default access permissions in the absence of an lpd.perms file is to accept all requests.
Trang 6using a variety of mechanisms, including PGP and Kerberos Consult the LPRng documentation for full details.
I l@ ve RuBoard
Trang 7I l@ ve RuBoard
13.7 CUPS
The Common Unix Printing System ( CUPS) is another project aimed at improving, and ultimately superceding, the traditional printing subsystems CUPS is distinguished by the fact that it was designed to address printing within a networking environment from the beginning, rather than being focused on printing within a single system Accordingly, it has features designed to support both local and remote printing, as well as printers directly attached to the network We will take a brief look at CUPS in this section The homepage for the project is http://www.cups.org
CUPS is implemented via the Internet Printing Protocol (IPP) This protocol is supported by most current printer manufacturers and operating systems IPP is implemented as a layer on top of HTTP, and it includes support for security-related features such as access control, user authentication, and encryption Given this structure, CUPS requires a web server on printer server systems.
Architecturally, CUPS separates the print job handling and device spooling functions into distinct modules Print jobs are given a identifier number and also have a number of associated attributes: their destination, priority, media type, number of copies, and so on As with other spooling subsystems, filters may be specified for print queues and/or devices in order to process print jobs The CUPS system provides many of them Finally, backend programs are responsible for sending print jobs to the actual printing devices.
CUPS also supports printer classes: groups of equivalent printers fed by a single queue (we've previously also referred to such entities as printer pools) CUPS extends this construct by introducing what it calls "implicit classes." Whenever distinct printers and/or queues on different servers are given the same name, the CUPS system treats the collection as a class, controlling the relevant entities as such In other words, multiple servers can send jobs to the same group of equivalent printers In this way, implicit classes may be used to prevent any individual printing device or server system from becoming a single point of failure Classes may be nested: a class can been a member of another class.
13.7.1 Printer Administration
CUPS supports the lpr , lpq , and lprm commands and the lp , lpstat , and cancel commands from the BSD and System V printing systems, respectively For queue and printer administration, it offers two options: command-line utilities, including a version of the System V lpadmin command, or a web-based
interface The latter is accessed by pointing a browser at port 631: for example, http://localhost:631 for the local system.
The following commands are available for managing and configuring print queues Note that all of them except lpinfo specify the desired printer as the argument to the -p option:
lpstat
View queue status.
accept and reject
Allow/prevent jobs from being sent to the associated printing device.
enable and disable
Allow/prevent new print jobs from being submitted to the specified queue.
lpinfo
Display information about available printers ( -v ) or drivers ( -m ).
lpadmin
Configure print queues.
Here is an example lpadmin command, which adds a new printer:
# lpadmin -plj4 -D"Finance LaserJet" -L"Room 2143-A" \
-vsocket://192.168.9.23 -mlaserjet.ppd
This command add a printer named lj4 located on the network using the indicated IP address The printer driver to be used is laserjet.ppd (several are provided
with the CUPS software) The -D and -L options provide descriptions of the printer and its location, respectively.
In general, the -v option specifies the printing device as well as the method used to communicate with it Its argument consists of two colon-separated parts: a connection-type keyword (which selects the appropriate backend module), followed by a location address Here are some syntax forms:
parallel:/dev/device Local parallel port
serial:/dev/device Local serial port
usb:/dev/usb/device Local USB port
ipp://address/port IPP-based network printer
lpd://address/DEVICE LPD-based network printer
Trang 8from a class, and -x to remove the print queue itself.
Under CUPS, printers need only be configured on the server(s) where the associated queues are located All clients on the local subnet will be able to see them once CUPS is installed and running on each system.
13.7.1.1 CUPS configuration files
CUPS maintains several configuration files, stored in the /etc/cups directory Most of them are maintained by lpadmin or the web-based administrative interface.
The one exception, which you may need to modify manually, is the server's main configuration file, cupsd.conf
Here are some sample annotated entries (all non-system-specific values are the defaults):
ServerName painters.ahania.com Server name.
ServerAdmin root@ahania.com CUPS administrator's email address.
ErrorLog /var/log/cups/error_log Log file locations.
AccessLog /var/log/cups/access_log
PageLog /var/log/cups/page_log Printer accounting data.
LogLevel info Log detail (other levels: debug, warn, error).
MaxLogSize 1048571 Rotate log files when current is bigger than this.
PreserveJobFiles No Don't keep files after print job completes
RequestRoot /var/spool/cups Spool directory.
User lp Server user and group owners.
Group sys
TempDir /var/spool/cups/tmp CUPS temporary directory.
MaxClients 100 Maximum client connections to this server
Timeout 300 Printing timeout period in seconds.
Browsing On Let clients browse for printers.
ImplicitClasses On Implicit classes are enabled.
Readers familiar with the Apache facility will notice many similarities to its main configuration file (httpd.conf ).
13.7.1.2 Access control and authentication
Printer access control, user authentication, and encryption are also enabled and configured in the cupsd.conf configuration file.[9]
[9] These features are somewhat in flux as of this writing, so there may be additional capabilities inyour version of CUPS Consult the CUPS documentation for details on the current state of things
Encryption is controlled by the Encryption entry:
Encryption IfRequested
The entry indicates whether or not to encrypt print requests (in order to use encryption, the OpenSSL library must be linked into the CUPS facility) The default is
to encrypt files if the server requests it; other values are Always and Never Additional keywords may be added as other encryption methods become available.
There are two main entries related to user authentication:
Method of authentication The default is Anonymous (perform no authentication) Other options are User (valid username and password are required),
System (user must also belong to the system group, which can be defined using the SystemGroup entry), and Group (user must also belong to the
group specified in the AuthGroupName entry).
The encryption- and user authentication-related entries are used to specify requirements for specific printers or printer classes These are defined via stanzas like
Trang 9[Encryption entry] The ordering here is not significant.
[Authentication entries]
[Access control entries]
</Location>
The pseudo-HTML directives delimit the stanza, and the item specified in the opening tag indicates the entities to which the stanza applies.[10] It can take one
of the following forms:
[10] Again, note the similarity to the Apache configuration file syntax
/ Defaults for the CUPS system.
/printers Applies to all non-specified printers.
/printers/name Applies to a specific printer.
/classes Applies to all non-specified classes.
/classes/name Applies to the specified class.
/admin Applies to CUPS administrative functions.
Here a some example stanzas (which also introduce the access control directives):
<Location /> System defaults.
Order Deny,Allow Interpret Allow list as overrides to Deny list.
Deny From All Deny all access
Allow From 127.0.0.1 except from the local host.
</Location>
<Location /printers>
Order Allow,Deny Interpret Deny list as exceptions to Allow list.
Allow From ahania.com Allow access from these domains
Allow From essadm.com
Deny From 192.168.9.0/24 but exclude this subnet.
</Location>
<Location /classes/checks> Applies to class named checks.
Encryption Always Always encrypt.
AuthType Digest Require valid user account and password.
AuthClass Group Restrict to members of the finance group.
AuthGroupName finance
Order Deny,Allow
Deny From All Deny all access
Allow From 10.100.67.0/24 except from this subnet.
</Location>
<Location /admin> Access for administrative functions.
AuthType Digest Require valid user account and password.
AuthClass System Limit to system group members.
Order Deny,Allow
Deny From All Restrict access to the local domain.
Allow From ahania.com
</Location>
Consult the CUPS documentation for information about the facility's other features as well as its installation procedure.
I l@ ve RuBoard
Trang 10I l@ ve RuBoard
13.8 Font Management Under X
On most current Unix systems, fonts are made available to applications via the X Window system (although some application packages manage their own font information) In this section, we will consider the main administrative tasks related to managing fonts.
In an ideal world, fonts would be something that users took care of themselves However, in this world, font handling under X is cumbersome enough that the system administrator often needs to get involved.
In this section, we consider font management using the standard X11R6 tools, and we refer to directory locations as defined by the normal installation of the package These facilities and locations are often significantly altered (and broken) in some vendors' implementations.
13.8.1 Font Basics
When you think of a font, you probably think of something like Times or Helvetica These names actually referred to font families containing a number of different
typefaces: for example, regular Times, italic Times, bold Times, bold italic Times, and so on At the moment, there are quite a few different formats for font files The most important distinction among them is between bitmap fonts and outline fonts Bitmap fonts store the information about the characters in a font as bitmap images, while outline fonts define the characters in a font as a series of lines and curves, comprising in this way mathematical representations of the component characters.
From a practical point of view, the main difference between these two font types is scalability Outline fonts can be scaled up or down any arbitrary amount and look equally good at all sizes In contrast, bitmap fonts do not scale well, and they look extremely jagged and primitive as they get larger For this reason, outline fonts are generally preferred to bitmap fonts.
To further complicate matters, there are two competing formats for outline fonts: Adobe Type 1 and TrueType In technical terms, the chief difference between them consists of the kind of curves used to represent the characters: Bezier curves and b-splines, respectively The other major difference between the two formats is price, with Type 1 fonts generally being significantly more expensive than TrueType fonts.
All of these different types of fonts are generally present under X The most important formats are listed in Table 13-7 , along with their corresponding file extensions.
Portable Compiled Font
Trang 11Table 13-7 Common font file formats
The PCF fonts are bitmap fonts (generally stored in compressed format) that come as part of the XFree86 system, typically located in directories under
/usr/X11R6/lib/X11/fonts or /usr/lib/X11/fonts The Speedo fonts were donated to the X Window system by Bitstream and are located in the same place The
Ghostscript fonts are Type 1 fonts installed with that facility (using a slight variation in format) In addition, there may be Type 1 and/or TrueType fonts present
on the system.
Type 1 fonts consist of multiple files The pfa and pfb files contain the actual font outline representation, in ASCII and binary format, respectively, and the afm file contains font metrics information in ASCII format Type 1 fonts on Unix systems generally use the binary pfb files (probably because they are smaller in size), but pfa files may also be used The corresponding afm file is also required in order to print.
The X window system uses a somewhat arcane naming convention for referring to fonts Here is its general syntax and an example for a font in the Octavian family:
-foundry-family-weight-slant-stretch-style-pixel-points-xres-yres-spacing-avgwidth-registry-encoding
-monotype-octavian mt-medium-i-normal 0-0-0-0-p-0-iso8859-1
The components have the following meanings The foundry [11] is the organization (often a commercial entity) that provided/sold the fo nt, Monotype in our
example The family indicates the overall grouping of typefaces to which this particular item belongs (for example, Times or Helvetica); our example is from the Octavian MT family (note that spaces commonly appear within the family name) The next few items indicate which member of the family this one is: weight is a keyword indicating the relative darkness of this typefaces with respect to other family members (medium, bold, light, black and so on), slant is a single character indicating whether this typeface is upright (r for roman, i for italic or o for oblique), stretch is a keyword indicating whether the typeface is expanded or
compressed with respect to normal lettering (normal, condensed, expanded and so on), and style is a keyword indicating any additional typographic style
information relevant to this typeface (e.g., expert, ornaments, oldstylefigures, etc.) Our example typeface is Octavian Italic (not bold, not condensed/expanded, and no additional style designation).
[11] As one of the technical reviewers noted, this term comes "from the days of moveable set typewhere a iron working foundry was responsible for manufacturing the type sets."
The remaining fields specify the default point size (points ), the body size in pixels at that point size (pixels ), the typeface's default horizontal and vertical resolution (xres and yres ), its spacing class (spacing : one of m for monospace/fixed width, c for character cell and p for proportional), a measure of the average width of the glyphs in the font (avgwidth ), and the character set use for coding the font (registry and encoding ) Most of the numeric fields tend to be set to zero
for outline fonts, indicating the font's default value should be used—as they are in our example—and the three remaining fields are generally set to the values shown in the example as well.
In most instances, you'll never need to construct one of these names by hand Instead, you can use utilities which create them for you automatically for various contexts However, if you ever do need to generate one yourself, you can find all the essential information by running the strings command on the (binary) font file and looking at the information displayed at the beginning of its output (if you have an ASCII font file, you can look at that file's contents directly).
For more general information about fonts, consult the FAQ from the comp.fonts newsgroup (version 2.1.5, dated August 1996, is the most recent, available at
www.nwalsh.com/comp.fonts/FAQ ) For additional information about TrueType fonts, consult the TrueType HowTo (available on the web at
pobox.com/~brion/linux/TrueType-HOWTO.html ).
13.8.2 Managing Fonts under X
We now turn to the question of how X applications locate fonts they need As we noted previously, the fonts that come with the X window system conventionally
reside under /usr/X11R6/lib/X11/fonts In fact, though, when an application needs a font to display on the screen, it checks the current font path to find it Traditionally, the default font path is defined in the XF86Config configuration file (generally located in /etc or /etc/X11 , with several links to other places) via
FontPath lines in the Files section:
Each successive FontPath entry adds an additional directory to the font path.
On more recent systems, these lines have been replaced by one like this:
Trang 12This indicates that a font server is in use, listening for font requests on TCP port 7100 on the local machine.[12] Additional FontPath entries may again be
present, specifying either local directories or ports on other computers The introduction of the font server in X11R5 made life easier since it allowed files to be shared between systems The font server process actually runs the xfs program.[13]
[12] Note that on some systems running RedHat Linux, the entry appears like one of these:
a bit later), and modifying the system boot scripts so that the server process is started automatically
However the default font path is set up, and individual user can always modify it via the xset command, using its fp option.
13.8.3 Adding Fonts to X
Adding fonts for use in screen display by the X Window system is very easy For Type 1 fonts, the procedure is as follows:
Create a directory to hold the new fonts (if necessary) and copy the font files there Generally, you will need to put in both the pfa or pfb file and the
.afm file there.
Generate the required configuration files, named fonts.dir and fonts.scale (although, in fact, these two files are identical for the case of Type 1 fonts).
This can be done manually, or you can use a utility to do it for you; some versions of the standard X mkfontdir command work well for this task, and the type1inst command is very reliable (available at http://sunsite.unc.edu/pub/Linux/X11/xutils/ ) Both of them are run from within the directory holding the new fonts.
The entry in the files corresponding to our Octavian Italic typeface looks like this (we've wrapped it to fit):
oci_ _ _ _ _.pfb -monotype-octavian mt-medium-i-normal 0-0-0-0-p-0-iso8859-1
The first item is the filename dictating the Type 1 font (oci_ _ _ _ _.pfb in this case), and the second item is the standard X typeface designation.
If you created a new font directory, add it to the font path If you are not using a font server, this is done by adding another FontPath entry to the
XF86Config file If you are using a font server, then you must edit an entry in its configuration file The xfs font server typically stores its configuration
file as /etc/X11/fs/config You'll need to an additional component to the catalogue list:
Restart the font server (e.g., a command like /etc/init.d/xfs restart ) Also restart any current X session.
Once this is all complete, the new fonts should be available to any application that uses the standard X font facilities You can verify that the fonts are installed correctly using the X commands xfontsel and xfd ; the gimp application provides another very pleasant way of exploring the new fonts The first two commands can also be useful for exploring what fonts are available on the system and displaying all the characters within a given typeface However, the latter job is better handled by the freely-available gfontview utility, whose output is displayed in Figure 13-10 This facility allows you to view a single character, a short string, or a palette containing every character within it (in the example display, I've been somewhat self-absorbed in my choice of test character and character string) You can get this utility at http://gfontview.sourceforge.net
Trang 13Printing the newly installed fonts introduces a few additional wrinkles In order to be printed, Type 1 fonts must be rendered (technically, rasterized) Under X, this is usually handled by the Ghostscript facility (http://www.ghostscript.org ), which must be configured for any new fonts.
Ghostscript font configuration occurs via its Fontmap configuration file, located in the /usr/share/ghostscript/ n.nn directory, where the final component of the path corresponds to the package version (under FreeBSD, the path begins at /usr/local/share ).
Here are some sample entries from this file:
is another name (indicated by an initial slash), then the first field becomes an alias—an alternate name—for the same typeface For example, the preceding
entries will result in the file n021003I.pfb being used when someone wants to print the Times-Roman font.[14]
[14] Occasionally, you will need to create aliases for fonts in order to get them to print properly Themost common example occurs with "regular" typefaces that do not have "Roman" in their name Thiscan confuse some environments and applications In such cases, creating an alias in the expectedformat will often do the trick For example:
In order to print our Octavian typeface, we need to add a line like the following to this file:
/OctavianMT-Italic (oci_ _ _ _ _.pfb) ;
The type1inst utility mentioned earlier creates a Fontmap file within the current directory along with the fonts.dir and fonts.scale files, making it easy to add
the required entries to the actual Ghostscript font configuration file.
The filename field may contain either an absolute path or a simple file name In the latter case, the Ghostscript font path will be searched for that file The
default path is set up when the facility is compiled and typically consists of subdirectories under /usr/share/fonts/default (e.g., ghostscript and Type1 ) You can make fonts available to Ghostscript by adding them to these existing locations (and modifying the current fonts.dir and fonts.scale files accordingly), or by using
a new location, which can be added to the Ghostscript path by setting the GS_LIB environment variable.
13.8.4 Handling TrueType Fonts
With TrueType fonts, the fun really begins Basically, the X font facilities and Ghostscript were designed around bitmap and Type 1 fonts and PostScript printing However, users tend to have access to lots of TrueType fonts, and they naturally want to use them on Unix systems Fortunately, support for TrueType fonts
Trang 14font servers have been merged into the main XFree86 distribution as modules See the "Fonts in Xfree86" document at http://www.xfree.org for full details (currently, http://www.xfree86.org/4.0.3/fonts.html ) as well as the X TrueType Server Project home page, http://x-tt.dsl.gr.jp , and the FreeType Project homepage, http://www.freetype.org
The module based on the excellent xfsft server can be included by editing the Modules section of the XF86Config file and adding a Load entry for the module
freetype
Once you have a TrueType-capable font server, the procedure for adding new TrueType fonts is almost identical to that for adding Type 1 fonts The difference lies in using the ttmkfdir utility instead of type1inst (available at http://www.joerg-pommnitz.de/TrueType/xfsft.html ; click on the link in the paragraph
referring to the "tool that creates the fonts.scale file").
Here are three fonts.dir entries for TrueType fonts, Eras Light and Eras Bold:
eraslght.ttf -itc-Eras Light ITC-medium-r-normal 0-0-0-0-p-0-iso8859-1
erasbd.ttf -itc-Eras Bold ITC-medium-r-normal 0-0-0-0-p-0-iso8859-1
:2:mincho.ttc -misc-mincho-
The final entry shows the method for referring to individual fonts with a TrueType Collection file.
For printing TrueType fonts from general X applications the best option is to use a version of Ghostscript which has been compiled with ttfont option, enabling
TrueType support with the facility (it must be a version 5 revision of Ghostscript ) In this case, you simply add entries as usual to the Fontmap file pointing to
the TrueType font files.
I l@ ve RuBoard
Trang 15I l@ve RuBoard
Chapter 14 Automating Administrative Tasks
Although extensive programming experience is seldom a requirement for a system administration position,writing shell scripts and other sorts of programs is nevertheless an important part of a system
administrator's job There are two main types of programs and scripts that you will be called upon to create:
Those designed to make system administration easier or more efficient, often by automating someprocess or job
Those that provide users with necessary or helpful tools that are not otherwise available to them.This chapter discusses scripts intended for both contexts
In general, automation offers many advantages over performing such tasks by hand, including the following:
Enhanced system efficiency
Time-consuming or resource-intensive tasks can be performed during off hours, freeing the system forusers during their normal work hours
We've already considered the cron facility, which runs commands and scripts according to a preset schedule(see Section 3.2) In this chapter, we'll begin by looking at some example shell scripts and then considersome additional programming/scripting languages and other automation tools
Laziness Can Be a Virtue
Lazy people write shell scripts.Laziness is a very important system administrative virtue when it
motivates you to create new tools and utilities that make your job easier, more efficient, or even
just more pleasant Ploddingly industrious people type the same commands over and over, day
after day; lethargic people write scripts to make the job go faster; truly lazy people develop
utilities and programs that make all kinds of jobs go faster (including ones they weren't even
thinking about when they started)
Writing shell scripts, Perl scripts, Expect scripts, or C programs will also force you to develop
another of the seven system administrative virtues:patience (you'll need it to see a sometimes
frustrating task through to its conclusion)
I l@ve RuBoard
Trang 16I l@ve RuBoard
14.1 Creating Effective Shell Scripts
In this section, we'll consider several different routine system administration tasks as examples of creatingand using administrative shell scripts The discussions are meant to consider not only these tasks in
themselves but also the process of writing scripts Most of the shell script examples use the Bourne shell, butyou can use any shell you choose; it's merely a Unixprejudice that "real shell programmers use the
Bourne/Korn/zsh shell," however prevalent that attitude/article of faith may be.[1]
[1] Once upon a time, the C shell had bugs that made writing administrative C shell scripts somewhatdicey Although the versions of the C shell in current operating systems have fixed these bugs, theattitude that the C shell is unreliable persists In addition, the C shell is considered poorly designed bymany scripting gurus
14.1.1 Password File Security
We discussed the various security issues surrounding the password file in Section 7.8 and Section 6.1 Thevarious commands used to check it and its contents could be combined easily in a shell script Here is oneversion (named ckpwd):
PATH="/bin:/usr/bin"; export PATH
cd /usr/local/admin/old # stored passwd file location
echo ">>> Password file check for `date`"; echo ""
echo "*** Accounts without passwords:"
# Look for extra system accounts
echo "*** Non-root UID=0 or GID=0 accounts:"
Trang 17sort </etc/passwd >tmp1
sort <opg >tmp2 # opg is the previously saved copy
echo "*** Accounts added:"
comm -23 tmp[1-2] # lines only in /etc/passwd
echo ""
echo "*** Accounts deleted:"
comm -13 tmp[1-2] # lines only in /opg
echo ""
rm -f tmp[1-2]
echo "*** Password file protection:"
echo "-rw-r r 1 root wheel>>> correct values"
ls -l /etc/passwd
echo ""; echo ">>> End of report."; echo ""
The script surrounds each checking operation with echo and other commands designed to make the outputmore readable so that it can be scanned quickly for problems For example, the grep command that looks
for non-root UID 0 accounts is preceded by an echo command that outputs a descriptive header Similarly,the grep command's output is piped to an awk command that removes the root entry from its output and
displays the remaining accounts or the string "None found" if no other UID or GID 0 accounts are present.Instead of using diff to compare the current password file with the saved version, the script uses comm
twice, to present the added and deleted lines separately (entries that have changed appear in both lists).The script ends with a simple ls command; the administrator must manually compare its output to thestring displayed by the preceding echo command However, this comparison also could be automated bypiping ls's output to awk and explicitly comparing the relevant fields to their correct values (I'll leave theimplementation of the latter as an exercise for the reader.)
Here is some sample output from ckpwd:
>>> Password file check for Fri Jun 14 15:48:26 EDT 2002
*** Accounts without passwords:
*** Password file protection:
-rw-r r 1 root system >>> correct values
-rw-r r 1 root system 1847 Jun 11 22:38 /etc/passwd
>>> End of report.
If you don't like all the bells and whistles, the script needn't be this fancy For example, its two sort, two
comm, and five other commands in the section comparing the current and saved password files could easily
be replaced by the diff command we looked at in Section 7.8 (and possibly one echo command to print aheader) In the extreme case, the entire script could consist of just the four commands we looked atpreviously:
#!/bin/sh
Trang 18Whatever approach you take, ckpwd needs to be run regularly to be effective (probably by cron).
14.1.2 Monitoring Disk Usage
It seems that no matter how much disk storage a system has, the users' needs (or wants) will eventuallyexceed it As we discuss in Section 15.6, keeping an eye on disk space is a very important part of systemmanagement, and this monitoring task is well suited to automation via shell scripts
The script we'll consider in this section—ckdsk—is designed to compare current disk use with what it wasyesterday and to save today's data for comparison tomorrow We'll build the script up gradually, startingwith this simple version:
#!/bin/sh
# ckdsk: compares current and saved disk usage
# saved data is created with du_init script
#
PATH="/bin:/usr/bin"; export PATH
cd /usr/local/admin/ckdsk
if [ ! -s du.sav ] ; then
echo "ckdsk: Can't find old data file du.sav."
echo " Recreate it with du_init and try again."
After making sure yesterday's data is available, this script checks the disk usage under the directory
/iago/home/harvey using du, saving the output to the file du.log Each line of du.log is fed by xargs toanother script, cmp_size[2], which does the actual comparison, passing it the arguments 40, 100, and
"du.sav," as well as the line from the du command Thus, the first invocation of cmp_size would look
something like this:
[2] On some systems, cmp_size could be a function defined in ckdsk; on others, however, xargs
won't accept a function as the command to run
cmp_size 40 100 du.sav 876 /iago/home/harvey/bin
Output from du begins with argument 4.
ckdsk ends by replacing the old data file with the saved output from today's du command, in preparation forbeing run again tomorrow
Trang 19This simple version of the ckdsk script is not very general because it works only on a single directory Afterlooking at cmp_size in detail, we'll consider ways of expanding ckdsk's usefulness Here is cmp_size:
#!/bin/sh
# cmp_size - compare old and new directory size
# $1 (limit)=min size for new dirs to be included in report
# $2 (dlimit)=min size change for old dirs to be included
# $3 (sfile)=pathname for file with yesterday's data
# $4 (csize)=current directory size
# $5 (file)=pathname of directory
# osize=previous size (extracted from sfile)
# diff=size difference between yesterday & today
PATH="/bin:/usr/bin"; export PATH
if [ $# -lt 5 ] ; then
echo "Usage: cmp_size newlim oldlim data_file size dir"
exit 1
fi
# save initial parameters
limit=$1; dlimit=$2; sfile=$3; csize=$4; file=$5;
# get yesterday's data
osize=`grep "$file\$" $sfile | awk '{print \$1}'`
if [ -z "$osize" ] ; then # it's a new directory
if [ $csize -ge $limit ] ; then # report if size >= limit
echo "new\t$csize\t$file"
fi
exit 0
fi
# compute the size change from yesterday
if [ $osize -eq $csize ]
# report the size change if large enough
if [ $diff -ge $dlimit ] ; then
echo "$osize\t$csize\t$file"
fi
cmp_size first checks to see that it was passed the right number of arguments Then it assigns its
arguments to shell variables for readability The first two parameters are cutoff values for new and existingdirectories, respectively These parameters allow you to tell cmp_size how much of a change is too small to
be interesting (because you don't necessarily care about minor disk usage changes) If the size of thedirectory specified as the script's fifth parameter has changed by an amount greater than the cutoff value,
Trang 20cmp_size prints the directory name and old and new sizes; otherwise, cmp_size returns silently.
cmp_size finds yesterday's size by greping for the directory name in the data file specified as its third
parameter (du.sav is what ckdsk passes it) If grep didn't find the directory in the data file, it's a new one,and cmp_size then compares its size to the new directory cutoff (passed in as its first argument) displayingits name and size if it is large enough
If grep returns anything, cmp_size then computes the size change for the directory by subtracting the
smaller of the old size (from the file and stored in the variable osize) and the current size (passed in as the fourth parameter and stored in csize) from the larger cmp_size then compares the size difference to theold directory cutoff (passed in as its second argument), and displays the old and new sizes if it is largeenough
cmp_size reports on directories that either increased or decreased in size by the amount of the cutoff Ifyou are only interested in size increases, you could replace the if statement that computes the value of the
diff variable with a much simpler one:
if [ $osize -le $csize ]
# chkdsk2 - multiple directories & per-directory cutoffs
PATH="/bin:/usr/bin"; export PATH
du_it( )
{
# $1 = cutoff in blocks for new directories
# $2 = cutoff as block change for old directories
# $3 = starting directory
# $4 = flags to du
abin="/usr/local/admin/bin"
du $4 $3 > du.tmp
cat du.tmp | xargs -n2 $abin/cmp_size $1 $2 du.sav
cat du.tmp >> du.log; rm du.tmp
Trang 21exit 1
fi
echo "Daily disk usage report for `date`"; echo ''
df
echo ''; echo "Old\tNew"
echo "Size\tSize\tDirectory Name"
This script uses a function named du_it to perform the du command and pass its output to cmp_size using
xargs The function takes four arguments: the cutoffs for old and new directories (for cmp_size), thestarting directory for the du command, and any additional flags to pass to du (optional)
du_it saves du's output into a temporary file, du.tmp, which it appends to the file du.log afterwards; du.log
thus accumulates the data from multiple directory checks and eventually becomes the new saved data file,replacing yesterday's version
The script proper begins by removing any old temporary files from previous runs and making sure its data
file (still hardwired as du.sav) is available It then runs df and prints some header lines for the output from
cmp_size This version of the script then calls du_it three times:
size of any directory (size or size change greater than or equal to one) In contrast, when checking the users'
home directories under /home, the report includes new directories of any size but only existing directories
that changed size by at least 1000 blocks
ckdsk ends by moving the accumulated output file, du.log, on to the saved data file, du.sav, saving the
current data for future comparisons
Here is some sample output from ckdsk:
Daily disk usage report for Tue Jun 11 09:52:46 EDT 2002
File system Kbytes used avail capacity Mounted-on
/dev/dsk/c1d1s0 81952 68848 13104 84% /
/dev/dsk/c1d1s2 373568 354632 18936 94% /home
/dev/dsk/c1d2s8 667883 438943 228940 66% /genome
Old New
Trang 22Size Size Directory Name
-The echo commands set off the output from cmp_size and make it easy to scan
This version of ckdsk requires new du_it commands to be added by hand The script could be refinedfurther by allowing this information to be external as well, replacing the explicit du_it commands with aloop over the directories and parameters listed in a data file:
cat du.dirs |
while read dir old new opts; do
# default old and new cutoffs to 1
if [ "$old" = "" ]; then
old=1; fi
if [ "$new" = "" ]; then
new=1; fi
if [ -n "$dir" ]; then # ignore blank lines
du_it $new $old $dir $opts
The cron facility is also the most sensible way to run ckdsk
14.1.3 Root Filesystem Backups and System Snapshots
Backing up the root filesystem is a task for which the benefits don't always seem worth the trouble Still, creating all of the changed system configuration files is also very time-consuming, and can be very
re-frustrating when you don't immediately recall which files you changed
An alternative to backing up the entire root filesystem—and other separate system filesystems like /usr and
Trang 23/var—is to write a script to copy only the few files that have actually changed since the operating system
was installed to a user filesystem, allowing the changed files to be backed up as part of the regular systembackup schedule without any further effort on your part Creating such a script is also a good way to becomethoroughly acquainted with all the configuration files on the system When selecting files to copy, includeanything you might ever conceivably change, and err on the side of too many rather than too few files.Here is a C shell script that performs such a copy:
#!/bin/csh
# bkup_sys - backup changed files from system partitions
unset path; setenv PATH "/bin:/usr/bin"
set dir_list=`cat /etc/bkup_dirs`
foreach dir ($dir_list)
echo "Working on $dir "
if (! -d $SAVE_DIR/$dir) mkdir -p $SAVE_DIR/$dir
set files=`file $dir/{,.[a-zA-Z]}* | \
egrep 'text|data' | awk -F: '{print $1}'`
if ("$files" != "") cp -p $files $SAVE_DIR/$dir:t
end
echo "Backing up individual files "
foreach file (`cat /usr/local/admin/sysback/bkup_files`)
if ("$file:h" == "$file:t") continue # not a full pathname
if ("$file:t" == "") continue # no filename present
if (! -d $SAVE_DIR/$file:h) mkdir -p $SAVE_DIR/$file:h
cp -p $file $SAVE_DIR/$file:h
end
echo "All done."
This script performs the backup in two parts First, it copies all text and binary data files from a list of
directories to a designated directory; file types are identified by the file command, and the grep commandselects ones likely to be configuration files (some extra files will get copied, but this is better than missingsomething) The default destination location is named for the current host and has a form like
/save/hamlet/sys_save; this location can be overridden by including an alternate location on the bkup_sys
command line The directory list comes from the file /etc/bkup_dirs, which would contain entries like /, /etc,
/etc/defaults, /etc/mail, /var/cron, and so on.
The final section of the script copies the files listed in /usr/local/admin/sysback/bkup_files, which holds the
names of individual files that need to be saved (residing in directories from which you don't want to saveevery text and data file) It uses the C shell :h and :t modifiers, which extract the head (directory portion)and tail (filename and extension), respectively, from the filename in the specified variable The first two lines
in this section make sure that the entry looks reasonable before the copy command is attempted
In both cases, files are stored in the same relative location under the destination directory as they are in the
Trang 24real filesystem (this makes them easy to restore to their proper locations) Subdirectories are created asnecessary under the destination directory The script uses cp -p to copy the files, which reproduces fileownership, protections, and access and modification times.
Copying files in this way is a protection against serious damage to a system filesystem (as well as againstaccidentally deleting or otherwise losing one of them) However, in order to completely restore the system,
in the worst case, you'll need to reproduce the structure as well as the contents of damaged filesystems To
do the latter, you will need to know what the original configuration was You can write a script to documenthow a system is set up
Here is an example from a FreeBSD system:
#!/bin/csh
# doc_sys - document system configuration FreeBSD version
unset path; setenv PATH "/sbin:/usr/sbin:/bin:/usr/bin"
echo ">>>Physical Disks" >> $outfile
grep "ata[0-9]+-" /var/run/dmegs.boot >> $outfile # Assumes IDE disks.
echo "" >> $outfile
echo ">>>Paging Space Data" >> $outfile
pstat -s >> $outfile
echo "" >> $outfile
echo ">>>Links in /" >> $outfile
file /{,.[a-zA-Z]}* | grep link >> $outfile
As this script illustrates, the commands you need to include tend to be very operating-system-specific Here
is a version for an AIX system (the common sections have been replaced with comments):
#!/bin/csh
# doc_sys - document system configuration AIX version
unset path; setenv PATH "/usr/sbin:/bin:/usr/bin"
Trang 25set output file and write header line
echo ">>>Physical Disks" >> $outfile
echo ">>>Volume Group Info" >> $outfile
# loop over volume groups
echo ">>>Logical Volume Details" >> $outfile
# loop over volume groups and then over the component LVs
foreach vg (`lsvg`)
foreach lv (`lsvg -l $vg | egrep -v ":|NAME" | awk '{print $1}'`)
lslv $lv >> $outfile
echo "===Physical Drive Placement" >> $outfile
lslv -l $lv >> $outfile echo "" >> $outfile
end
end
echo "" >> $outfile
echo ">>>Defined File systems" >> $outfile
lsfs >> $outfile echo "" >> $outfile
links in / listed here
echo ">>>System Parameter Settings" >> $outfile
lsattr -E -H -l sys0 >> $outfile
lslicense >> $outfile # number of licensed users
This version of the script also provides information about the volume group and logical volume layout on thesystem
Table 14-1 lists commands that will provide similar information for the Unix versions we are considering:
Table 14-1 System information commands
Trang 26Version Disk data Swap space data System parameters
HP-UX ioscan -f -n -C disk swapinfo -t -a -m /usr/lbin/sysadm/system_prep -s system
See Section 10.3 for the Logical Volume Manager commands for the various systems
Sometimes more than just a simple command is needed to complete one of these tasks For example, thefollowing script displays all the system parameters under Tru64:
14.1.4 A Few More Tricks
The following script illustrates a couple of other useful tricks when writing shell scripts It polls various siteswith which the local system communicates to exchange mail and runs a few times a day via the cron
Trang 27exit 0
This script loops over the list of hosts in the file mail_list in the current directory Let's consider how it works when the current host is lucia The if statement determines whether the file /etc/.no_lucia exists If it does, the host lucia is not polled Using a file in this way is a very easy mechanism for creating script features that
can be turned on or off easily without having to change the script itself, the way it is called from another
script, any crontab entries using it, and so on When I don't want lucia to be polled (usually because its
owner has turned it off during an out-of-town trip, and I hate seeing dozens of failure messages piling up), Isimply run the command touch /etc/.no_lucia Deleting the same file reinstates polling on a regularbasis
The second technique consists of using an empty file's modification time to store a date In this script, the
touch command in the if loop records when the most recent poll of system lucia took place The date it
occurred can be quickly determined by running:
$ ls -l /usr/local/admin/mail/last_lucia
Such time-stamp files can be used in a variety of contexts:
Backups
If you create a time-stamp file at the beginning of a backup operation, you can use a -newer clause
on a find command to find all files modified since then for a subsequent backup
Testing
When you want to find out what files a particular program modifies, create a time-stamp file in /tmp,
run the program, and then find files newer than the time-stamp file once the program finishes (thisassumes you are on an otherwise idle system)
Files modified since an operating system installation or upgrade
Creating a time-stamp file at the end of an operating system installation or upgrade will enable youeasily to determine which files you have modified with respect to the versions on the distributionmedia
14.1.5 Testing and Debugging Scripts
The following list describes strategies for testing and debugging scripts:
Build the script up gradually Start by getting a simple version running—without arguments and
handling only the easiest case—and then add the bells and whistles We've seen this strategy in actionseveral times in this chapter already
Test and debug the logic independently of the functionality if possible One way to do this is to place an
"echo" in front of every substantive command in the script, as in this fragment:
if [ some condition ]; then
Trang 28can replace entire functions with an echo command:
In general, inserting an echo command is a good way to see where you are in a script, to track
variable values, and so on In some cases, a construct like the following will be helpful:
echo "===${variable}==="
This sort of technique is useful when you are having trouble with a variable that may contain internalwhite space
Use the shell's -v option This option displays each script line as it is executed, and it will sometimes
indicates how the flow of a script is proceeding
Perform testing and debugging on local copies of system files The script will modify the copied files
rather than the real ones For example, if the script you are writing alters /etc/passwd, develop the script using a local copy of /etc/passwd rather than the real thing.
Use small cases for initial tests.
Operate on a single item at first, even if the script is designed to work on a large collection of items.Once that version is working, alter it to work for multiple items
Don't forget to test boundary conditions For example, if a script is designed to alter several user
accounts, make sure it works for one user account, two user accounts, zero user accounts, and many,many user accounts
Assume things will go wrong In general, include as much error-checking code in the script as possible,
making sure that the script does something reasonable when errors occur
Write for the general case Not only will this give you more powerful tools and meta-tools that you can
use over and over, but it is also no harder than coming up with a solution for one specific problem Infact, if you take a little time to step back from the specifics to consider the general task, it is ofteneasier
I l@ve RuBoard
Trang 29I l@ve RuBoard
14.2 Perl: An Alternate Administrative Language
Perl[3] is a free programming language created by LarryWall and currently developed and maintained by acore group of talented programmers (see http://www.perl.org, http://www.perl.com and
http://www.cpan.org for more information) Perl has become quite popular in recent years It contains manyfeatures that make it very well suited to writing scripts for system administrative tasks, including the
following:
[3] The name has various meanings, official and otherwise Two frequently cited by its author arePractical Extraction and Report Language and Pathologically Eclectic Rubbish Lister
It combines the short development time of traditional shell programming with some of the best aspects
of high-level languages such as C For example, Perl contains well-implemented arrays (unlike anyshell) and an impressive range of built-in functions, and it also includes the ability easily to run
standard Unix commands and use filename wildcards (as in a shell)
It provides things that are missing from most or all shells, including string functions, built-in
arithmetic, and general regular expression support
Handling many simultaneous open files is a breeze
It offers enhanced security features over standard shells
Perl features come from a variety of sources, including standard shells, C, Fortran, Basic, Pascal, awk, and
sed I don't think Larry's managed to use any COBOL features yet, but I've been wrong before
To get started using Perl, I recommend the following books:
Learning Perl, by Randall L Schwartz and Tom Phoenix (O'Reilly & Associates), and Effective Perl Programming, by Joseph N Hall with Randal L Schwartz (Addison-Wesley).
If you are interested in incorporating a graphical interface into Perl scripts, consult Learning Perl/Tk by
Nancy Walsh (O'Reilly & Associates)
For examples of using Perl for system administration tasks, see Perl for System Administration by
David N Blank-Edelman (O'Reilly & Associates)
14.2.1 A Quick Introduction
The best way to see what Perl has to offer is to look at a few Perl programs We'll begin with dr, a Perl script
I wrote to make theAIX dosread command worth using By default, dosread copies a single file from a DOSdiskette, and it requires that you specify both the DOS filename and the local filename (and not just a targetdirectory) Of course, what one often wants to do is to copy everything on a diskette; this Perl script copiesall the files on a diskette to the current directory, translating the destination filenames to lowercase:
#!/usr/bin/perl -w Executable location varies.
Trang 30# dr - copy all the files on a DOS diskette
# store the list of files on the diskette
@files = `dosdir | egrep -v "^(Free|There)"`;
foreach $f (@files) { # loop over files
chop $f; # remove newline char
The first command looks almost like a C shell command It runs the command in back quotes and stores the
output in the array @files (the AIX dosdir command lists the files on a diskette, and the egrep commandthrows away the summary line) Names of numerically indexed arrays begin with an @ sign when the entirearray is referenced as a whole Note also that Perl statements end with a semicolon
Perl scalar variable names always begin with a dollar sign, as the next few commands illustrate; no specialsyntax is needed to dereference them The remainder of the script is a foreach loop; the commands within
the loop are enclosed in curly braces (as in C) The loop variable is $f, and $g eventually holds a lowercase version of the name in $f.
The final two commands do the actual work The print command displays a string like the following foreach file on the diskette:
The foreach statement is still intelligible, but the other commands require some explanation Perl provides
a default variable that is used in commands where a variable is needed but none is specified; the name of
this variable is $_ (dollar-underscore) $_ is being used as the loop variable and as the argument to chop.The \L construct in the system command translates $_ to lowercase This system command is more
general than the one in the previous version It passes any arguments specified to the script — stored in the
array @ARGV—on to dosread and uses $_ as both of dosread's arguments; filenames on diskette aren'tcase-sensitive, so this works fine
The two versions of dr illustrate an important Perl principle: there's more than one way to do it (the
Perlslogan)
Trang 3114.2.2 A Walking Tour of Perl
wgrep is a tool I wrote for some users still longing for the VMS Search command they used years
previously wgrep stands for windowed grep, and the command searches files for regular expression
patterns, optionally displaying several lines of context around each matching line Like the command it wasdesigned to imitate, some of its options will strike some purists as excessive, but it will also demonstratemany of Perl's features in a more complex and extended context
Here is the usage message for wgrep:
Usage: wgrep [-n] [-w[b][:a] | -W] [-d] [-p] [-s] [-m] regexp file(s)
-n = include line numbers
-s = indicate matched lines with stars
-wb:a = display b lines before and a lines after each matched
line (both default to 3)
-W = suppress window; equivalent to -w0:0
-d = suppress separation lines between file sections
-m = suppress file name header lines
-p = plain mode; equivalent to -W -d
-h = print this help message and exit
Note: If present, -h prevails; otherwise, the rightmost option wins
in the case of contradictions.
Here is a sample of wgrep's most baroque output format, including line numbers and asterisks indicatingmatched lines, in addition to headers indicating each file containing matches and separators betweennoncontiguous groups of lines within each file:
wgrep -n -s -w1:1 chavez /etc/passwd /etc/group
# wgrep - windowed grep utility
$before = 3; $after = 3; # default window size
$show_stars = 0;
$show_nums = 0;
$sep = "**********\n";
Trang 32$show_fname = 1;
$show_sep = 1;
# loop until an argument doesn't begin with a "-"
while ($ARGV[0] =~ /^-(\w)(.*)/) {
$arg = $1; # $arg holds the option letter
This while statement tests whether the first element of @ARGV (referred to as $ARGV[0] because array
element references begin with a $ sign)—the array holding the command-line arguments — matches thepattern contained between the forward slashes: ^-(\w)(.*) Most of the elements of the pattern arestandard regular expression constructs; \w is a shorthand form for [a-zA-Z0-9_] Within a regular
expression, parentheses set off sections of the matched text that can be referred to later using the variables
$1 (for the first matched section), $2, and so on The next line copies the first matched section—the option
letter—to the variable $arg.
The next portion of wgrep forms the remainder of the body of the while loop and processes the availableoptions:[5]
[5] There are easier ways to parse lettered command options, but the point of this form is to illustratesome simple Perl The Getopt module is one popular choice for this task
if ($arg eq "s") { $show_stars = 1; }
elsif ($arg eq "n") { $show_nums = 1; }
elsif ($arg eq "m") { $show_fname = 0; }
elsif ($arg eq "d") { $show_sep = 0; }
elsif ($arg eq "h") { &usage(""); }
else { &usage("wgrep: invalid option: $ARGV[0]");
} # end of if command
shift; # go on to next argument
} # end of foreach loop
The foreach loop contains a long if-then-else-if construct, illustrating Perl's eclectic nature In
general, conditions are enclosed in parentheses (as in the C shell), and they are formed via Bourne shell-likeoperators (among other methods) No "then" keyword is required because the commands comprising the if
body are enclosed in curly braces (even when there is just a single command) Most of the clauses in this if
statement set various flags and variables appropriately for the specified options The clause that processesthe -w option illustrates a very nice Perl feature, conditional assignment statements:
split(/:/,$2);
Trang 33$before = $_[0] if $_[0] ne '';
The split command breaks the second matched section of the option—indicated by $2—into fields using a
colon as a separator character (remember the syntax is, for example, -w2:5), storing successive fields into
the elements of the default array @_ The following line sets the value of $before to the first element,
provided that it is not null: in other words, provided that the user specified a value for the window preceding
a matched line
The final else clause calls the usage subroutine when an unrecognized option is encountered (the
ampersand indicates a subroutine call) The shift command following the if statement works just as it
does in standard shell, sliding the elements of @ARGV down one position in the array.
The next section of wgrep processes the expression to search for:
&usage("missing regular expression") if ! $ARGV[0];
$regexp = $ARGV[0];
shift;
$regexp =~ s,/,\\/,g; # "/" > "\/"
# if no files are specified, use standard input
if (! $ARGV[0]) { $ARGV[0] = "STDIN"; }
If @ARGV is empty after processing the command options, the usage subroutine is called again Otherwise,
its first element is assigned to the variable $regexp, and another shift command is executed The second
assignment statement for $regexp places backslashes in front of any forward slashes that the regular
expression contains (since the forward slashes are the usual Perl pattern delimiter characters), using asyntax like that of sed or ex
After processing the regular expression, wgrep handles the case where no filenames are specified on thecommand line (using standard input instead) The next part of the script forms wgrep's main loop:
LOOP:
foreach $file (@ARGV) { # Loop over file list
if ($file ne "STDIN" && ! open(NEWFILE,$file)) {
print STDERR "Can't open file $file; skipping it.\n";
next LOOP; # Jump to LOOP label
This foreach loop runs over the remaining elements of @ARGV, and it begins by attempting to open the
first file to be searched The open command opens the file specified as its second argument, defining the file
handle—a variable that can be used to refer to that file in subsequent commands—specified as its first
argument (file handles are conventionally given uppercase names) open returns a nonzero value on
success If the open fails, wgrep prints an error message to standard error (STDIN and STDERR are the filehandles for standard input and standard error, respectively) and the file is simply skipped
The variable $fhandle is set to "STDIN" or "NEWFILE", depending on the value of $file, using a C-style
conditional expression statement (if the condition is true, the value following the question mark is used;
Trang 34otherwise, the value following the colon is used) This technique allows the user to specify STDIN on thecommand line anywhere within the file list.
Following a successful file open, some other variables are initialized, and the clear_buf subroutine is called
to initialize the array that will be used to hold the lines preceding a matched line The call to clear_buf
illustrates an alternate form of the if statement:
&clear_buf(0) if $before > 0;
The file is actually searched using a while loop It may be helpful to look at its logic in the abstract beforeexamining the code:
while there are lines in the file
if we've found a match already
if the current line matches too
print it and reset the after window counter
but if the current line doesn't match
if we are still in the after window
print the line anyway
otherwise
we're finally out of the match window, so reset all flags
and save the current line in the before buffer
otherwise we are still looking for a matching line
if the current line matches
print separators and the before window
print the current line
set the match flag
but if the current line doesn't match
save it in the before buffer
at the end of the file, continue on to the next file
Here is the part of the while loop that is executed once a matching line has been found The construct
<$fhandle> returns each line in turn from the file corresponding to the specified file handle:
while (<$fhandle>) { # loop over the lines in the file
++$lnum; # increment line number
if ($matched) { # we're printing the match window
if ($_ =~ /$regexp/) { # if current line matches pattern
$naft = 0; # reset the after window count,
&print_info(1); # print preliminary stuff,
print $_; # and print the line
}
else { # current line does not match
if ($after > 0 && ++$naft <= $after) {
# print line anyway if still in the after window
&print_info(0); print $_;
}
else { # after window is done
$matched = 0; # no longer in a match
$naft = 0; # reset the after window count
Trang 35# save line in before buffer for future matches
push(@line_buf, $_); $nbef++;
} # end else not in after window
} # end else curr line not a match
} # end if we're in a match
The while loop runs over the lines in the file corresponding to the file handle in the $fhandle variable; each line is processed in turn and is accessed using the $_ variable This section of the loop is executed when
we're in the midst of processing a match: after a matching line has been found and before the window
following the match has been finished This after window is printed after the final matched line that is found
within the window; in other words, if another matching line is found while the after window is being
displayed, it gets pushed down, past the new match The $naft variable holds the current line number within the after window; when it reaches the value of $after, the window is complete.
The print_info subroutine prints any stars and/or line numbers preceding lines from the file (or nothing ifneither one is requested); an argument of 1 to print_info indicates a matching line, and 0 indicates anonmatching line
Here is the rest of the while loop, which is executed when we are still looking for a matching line (andtherefore no lines are being printed):
else { # we're still looking for a match
if ($_ =~ /$regexp/) { # we found one
$matched = 1; # so set match flag
# print file and/or section separator(s)
print $sep if $matched2 && $nbef > $before && $show_sep && $show_fname; print "********** $file **********\n" if ! $matched2++ && $show_fname; # print and clear out before buffer and reset before counter
&clear_buf(1) if $before > 0; $nbef = 0;
&print_info(1);
print $_; # print current line
}
elsif ($before > 0) {
# pop off oldest line in before buffer & add current line
shift(@line_buf) if $nbef >= $before;
push(@line_buf,$_); $nbef++;
} # end elseif before window is nonzero
} # end else not in a match
} # end while loop over lines in this file } # end foreach loop over list of files
exit; # end of script proper
Several of the print commands illustrate compound conditions in Perl In this section of the script, the
variable $nbef holds the number of the current line within the before window; by comparing it to $before, we
can determine whether the buffer holding saved lines for the before window is full (there's no point in saving
more lines than we need to print once a match is found) The array @line_buf holds these saved lines, and
the push command (which we saw earlier as well) adds an element to the end of it The immediately
preceding shift(@line_buf) command shifts the elements of this array down, pushing off the oldest
saved line, making room for the current line (stored in $_).
Here is the subroutine print_info, which illustrates the basic structure of a Perl subroutine:
sub print_info {
Trang 36print $_[0] ? "* " : " " if $show_stars;
print $lnum," " if $show_nums;
}
Any arguments passed to a subroutine are accessible via the default array @_ This subroutine expects a
zero or one as its argument, telling it whether the current line is a match or not—and hence whether to print
a star or all spaces at the beginning of the line when $show_stars is true The subroutine's second statement
prints line numbers if appropriate.[6]
[6] Yes, this is an ugly kludge from my early Perl days A more elegant solution is left as an exercise forthe reader But don't miss the lesson that scripts don't have to be perfect to be effective
Subroutine clear_buf is responsible for printing the before window and clearing the associated array,
# if we're printing line numbers, fiddle with the counter to
# account for the before window
if ($show_nums) {
$target = $lnum - ($#line_buf + 1);
}
$lnum = "00000";
# yes, we're really counting back up to the right number
# to keep correct number format cycles are cheap
while ($i++ < $target) { ++$lnum; } }
while ($j <= $#line_buf) { # print before window
print STDERR "Usage: wgrep [-n] "
many more print commands
exit;
}
14.2.3 Perl Reports
Besides being a powerful programming language,Perl can also be used to generate attractive reports Here is
a fairly simple example:
Trang 37stein (0) /chem/1/stein 4982K ** UID=0
swenson (508) /chem/1/Swenson deleted
vega (515) /home/vega 100K ** CK PASS
This report was produced using format specifiers, which state how records written with the write commandare to look Here are the ones used for this report:
#!/usr/bin/perl -w
# mon_users - monitor user accounts
# header at the top of each page of the report
The first format statement is the header printed at the top of each page, and the second format statement
is used for the lines of the report Format specifications are terminated with a single period on a line Thesecond format statement indicates that the variables $uname, $home_dir, $disk, and $warn will be written
on each output line, in that order (the variables are defined elsewhere in the script) The line containing thestrings of greater-than and less-than signs indicates the starting positions, lengths, and internal justification
of the report's fields (text within a field is justified the way the angle bracket points)
Here is the rest of the script used to produce the report:
open (PASSWD, "/etc/passwd") || die "Can't open passwd: $!\n";
# remove newline, parse line, throw out uninteresting entries
if ($uname eq "root" || $uname eq "nobody" ||
Trang 38$warn = ($uid == 0 && $uname ne "root") ? "** UID=0" : "";
$warn = ($pass ne "!" && $pass ne "*") ? "** CK PASS" : $warn;
# = means string concatenation
$uname = " ($uid)"; # add UID to username string
# run du on home directory & extract total size from output
if (-d $home_dir && $home_dir ne "/") {
$du = `du -s -k $home_dir`; chop($du);
($disk,$junk) = split(/\t/,$du); $disk = "K";
This script introduces a couple of new Perl constructs which are explained in its comments
14.2.4 Graphical Interfaces with Perl
Users greatly prefergraphical interfaces to traditional, text-based ones Fortunately, it is very easy toproduce them with Perl using the Tk module Here is a simple script that illustrates the general method:
#!/usr/bin/perl -w
use Tk; # Use the Tk module.
# Read message-of-the-day file.
open MOTD, "/usr/local/admin/motd.txt" || exit;
# Window's text area.
$text=$main->Scrolled('Text', -relief => "sunken",
-borderwidth => 2, -setgrid => "true");
$text->insert("1.0", "$text_block");
$text->pack(-side=>"top", -expand=>1, -fill=>"both");
# Window's status area (bottom).
$status = $main->Label(-text=>"Last updated on $date",
Trang 39-relief=>"sunken", -borderwidth=>2,
-anchor=>"w");
$status->pack(-side=>"top", -fill=>"x");
# Add a Close button.
$button=$main->Button(-text => "Close Window",
# exit when button is pushed:
-command => sub{exit});
$button->pack;
MainLoop; # Main event loop: wait for user input.
The script has three main parts: processing the text file, creating and configuring the window, and the eventloop The first section reads the text file containing the message of the day, extracts the first field from thefirst line (assumed to hold the data the file was last modified), and concatenates the rest of its contents into
the variable $text_block.
The next section first creates a new window (via the new MainWindow function call) and then creates a labelfor it (assigning text to it), a text area in which text will be automatically filled, a button (labeled "CloseWindow"), and a status area (again, text is assigned to it) Each of these components is activated using the
pack method (function)
Finally, the third section, consisting only of the MainLoop command, displays the window and waits for userinput When the user presses the button, the routine specified to the button's command attribute is called;here, it is the Perl exit command, so the script exits when the button is pushed
Figure 14-1 illustrates the resulting window
Figure 14-1 Example Perl/Tk output
Note that the fill algorithm used for a simple text area is imperfect
More complex Perl/Tk programs, including ones accepting user input, are not fundamentally different fromthis one
Trang 40I l@ve RuBoard