Cover V13, i01

Article
Sidebar

jan2004.tar

Python in Systems Administration: Part II -- Step up from Shell

Cameron Laird

What kinds of problems are suited to Python? As a first approximation, think of Python the way you do Perl. Although far more Unix systems administrators currently work in Perl, the two languages are roughly comparable, for our purposes. Many of the differences between them are subjective, in the sense that experienced programmers simply find the features of one or the other fit their own habits of thinking better, although they're equally capable or provide the same formal functionality. This second installment in my series on "Python in Systems Administration" explains the parallels, then spotlights instances where Python might serve you better.

An example will show how similar they are, and how they differ from shell. Here I'm using "shell" to mean the language /bin/sh interprets, which is also compatible with bash, ksh, and other alternatives.

Reporting for Duty

In her popular book, Essential System Administration, Æleen Frisch lauds Perl for its ability, among other things, "to generate attractive reports". She demonstrates that (pages 368-369 of the Second Edition), with a simple table that tells the disk-space usage of different home directories.

Knowledgeable systems administrators do commonly turn to Perl when preparing reports. Many times, as in this case, it's for work that shell can do -- but with more difficulty. Python's just as easy as Perl, though.

To warm up to this problem, first consider this translation into shell of the Perl implementation Frisch provides:

#!/bin/sh


PASSWD=/etc/passwd
if [ ! -f $PASSWD ]
then
    echo "Can't open $PASSWD."
    exit 1
fi

FORMAT="%-18s  %-17s  %10s %-9s\n"
printf "$FORMAT" "" "" "Disk " ""
printf "$FORMAT" "Username (UID)" "Home Directory" \
                           Space Security
echo "---------------------------------------------\
---------------"
export IFS=:
cat $PASSWD | while read UNAME PASS xUID xGID JUNK \
                              HOME_DIR JUNK2
do
    case $UNAME in
        root|nobody|uu*)
            continue;;
    esac
    if [ $xUID -le 100 ] && [ $xUID -gt 0 ]
    then
        continue
    fi
    if [ $PASS != '!' ] && [ $PASS != '*' ] && [ $PASS != 'x' ]
    then
        WARN="** CK PASS";;
    elif [ $xUID -eq 0 ] && [ $UNAME != root ]
        WARN="** UID=0*"
    else
            WARN=""
    fi
    if [ -d $HOME_DIR ] && [ $HOME_DIR != / ]
    then
            # It's an error in the original that it
            # lacks stderr redirection.
        DISK='du -s -k $HOME_DIR 2>/dev/null | \
                              awk '{ print $1 }''
        if [ x$DISK == x ]
        then
            DISK=unknown
        else
            DISK=${DISK}K
        fi
    else
        DISK=skipped
    fi
    printf "$FORMAT" "$UNAME ($xUID)" $HOME_DIR \
                               $DISK $WARN
done
The output is the same report:

                                            Disk           
Username (UID)      Home Directory          Space Security
------------------------------------------------------------
lpd (104)           /                     skipped
sanders (464)       /home/sanders         725980K
stein (0)           /chem/1/stein           4982K  ** UID=0
   ...
as Frisch's Perl version produced. Does Perl improve on this? It's a few lines shorter. Perl -- and Python, as I'll illustrate -- pull ahead of shell when there's string-"wrangling" to do, or arithmetic. Frisch used a substr() function in her implementation, but I think the shell's wild-card match against "uu*" is even clearer than her coding.

Shell's branching is clumsy, which accounts for all Perl's advantage in conciseness in this example. Perl, Python, and other general-purpose languages have at least two capabilities that shell lacks entirely or exhibits only in a difficult form: data structuring, so that "records" or data values more complicated than primitive numbers and strings can be easily manipulated; and direct connections to OS-level operations. Among other things, the latter means that Perl and Python can easily operate on multiple files simultaneously.

Here's a Python script that generates the same report:

#!/usr/local/bin/python

import os,sys

passwd="/etc/passwd"
if not os.path.isfile(passwd):
    print "Can't open %s." % passwd
    sys.exit(1)


format="%-18s  %-17s  %10s %-9s"
print format % ("", "", "Disk ", "")
print format % ("Username (UID)", "Home Directory",
                           "Space", "Security")
print "---------------------------------------------\
---------------"
for line in open(passwd).readlines():
    (uname, xpass, uid, gid, junk, home_dir, junk2) = line.split(':')
    if uname == 'root' or uname == 'nobody' or uname[0:2] == 'uu':
        continue
    uid = int(uid)
    if uid <= 100 and uid > 0:
        continue
    if uid == 0 and uname != 'root':
        warn = "** UID=0"
    elif xpass != '!' and xpass != '*' and xpass != 'x':
        warn = "** CK PASS"
    else:
        warn = ""
    if os.path.isdir(home_dir) and home_dir != '/':
        disk = os.popen("du -s -k %s 2>/dev/null" %
                         home_dir).read().split('\t')[0]
        if disk == '':
            disk = "unknown"
        else:
            disk += "K"
    else:
        disk = "skipped"
    print format % ("%s (%s)" % (uname, uid), home_dir, \
           disk, warn)
Is this an improvement on shell and Perl? I think so. Python's line count happens to be smaller than that for either of the other two languages, and I think it's a bit "cleaner", too.

Six-month Test

I recognize that these are largely subjective matters. While ease of learning and "comprehensibility" were explicit design goals of Python from its origin in a way they never were for shell and Perl, experts in all three languages have no trouble reading and understanding the scripts we're considering in this series. A rigorous demonstration of the general superiority of one syntax or another is possible, I believe, but it would take a great deal of research and discipline.

Far less expensive is simply to try these languages for yourself. The first installment in this series (http://www.samag.com/documents/s=8964/sam0312a/0312a.htm) explained how easy it is to begin experimenting with Python. You don't have be an expert on how Perl and Python compare for all systems administrators; learn instead which one suits you best.

If you're like many other systems administrators I've met, you'll find that Python particularly suits you for its success at "the six-month test". A significant portion of the people who program only occasionally with Perl complain to me that they have trouble reading and therefore maintaining what they've written after the fact. They're good enough with the language to write what they need, but find that when they return to a particular script -- six months later, perhaps -- it no longer makes sense.

Quite a few of these systems administrators cherish Python because they find it easy to pick up again after an extended interval away from it. Maybe it will have that advantage for you.

One-liners

Perl is justly celebrated for its success in a variety of other roles, including Web scripting, network programming, and database development. As with report generation, Python substitutes well in each of these roles. One that's particularly helpful to systems administrators is "one-liners" -- the brief, disposable small scripts that take care of the miscellaneous chores that arise during typical administration work.

Python often does in three or five lines what Perl does in one. Perl builds in clever devices that make it easy to iterate over filenames, and to combine operations on a single line. Python emphasizes regularity more, and often demands explicitness where Perl builds in commonly used defaults. A file-renaming example illustrates that.

We often need to move files around within a file system, for administrative purposes. Here's a typical task -- move all files with the .jpg suffix found in directory DIR1 to DIR2, while simultaneously changing their names according to the model:

bridge.jpg   -> bridgeB.jpg
airplane.jpg -> airplaneB.jpg
In Python, a good way to accomplish this is with:

import glob, os

os.chdir(DIR1)
for file in glob.glob('*.jpg'):
      (base, extension) = os.path.splitext(file)
      os.rename(file, os.path.join(DIR2, base + 'B.jpg'))
How does that compare with the way you've been doing such tasks without Python?

Immediate Execution

Part II of "Python in Systems Administration" has emphasized that Python can be used for jobs where you'd first think of shell or Perl. A final aspect of this similarity has to do with interactive use.

Much of our work is "on the command-line" -- rather than compose a script saved in a file, which we run "batch", we just type commands and have the shell interpret them on the spot. Perl has this capability, by the way, although very few Perl users seem to know it. It's possible to enter a Perl shell, and have Perl statements executed interactively. It's an unusual way to work, though; most systems administrators think of immediate execution only in terms of their base shell. For more on this subject, see the sidebar "Notes on Interactive Perl".

Python changes that. Python users frequently work with the base Python shell -- just type "python" and all your commands will be interpreted interactively. This makes it handy to experiment.

Are you trying out different ideas for making a temporary file with a random component in the name? Try out your ideas "live":

# python
Python 2.2 (#1, 11/12/02, 23:31:59)
[GCC Apple cpp-precomp 6.14] on darwin
Type "help", "copyright", "credits" or "license" for more information.
>>> import random, string
>>> def a(head):
...   return head + ''.join([random.choice(string.digits) for \
  i in range(4)])
...
>>> a("name")
'name4943'
>>> a("name")
'name2707'
>>> a('longname')
'longname6525'

When you have a clear goal, but aren't sure how to reach it, this sort of "exploratory" programming can help you reach your goal incrementally.

It's even possible to take this to the extreme of doing all your work in Python. IPython (http://ipython.scipy.org/) repackages the standard Python language so it can realistically substitute for more standard shells such as bash and ksh. IPython's only for enthusiasts, I think; tossing out standard shell isn't a step to take lightly. Its availability does underline, however, Python's flexibility and aptness for the kinds of jobs usually given to shell.

In all these different ways, then, Python can substitute for the shell and Perl processors systems administrators more commonly use. Next time you start to solve a problem with Perl or shell, take a moment to consider whether Python might do the job just as well, and leave you with a readable and maintainable solution afterward.

Next month, in Part III in this series, I'll look at how Python solves the thorny problems involving password automation that often arise in systems administration. Would you like to learn a Python answer for a question you face? Write me at: claird@phaseit.net.

Cameron Laird, a vice president at consultancy Phaseit, Inc. (http://phaseit.net/), is a frequent contributor to Sys Admin magazine, and a columnist for UnixReview.com.