SunExpert Magazine, May 1997, pages 90-94.

Work: We Use Vi to Edit Web Pages

Jeffreys Copeland & Haemer

Editing Web Pages

We're going to show you how to turn vi into a wysiwyg HTML editor. (WYSIWYG, for those who aren't veterans of editor or word-processor wars, stands for What You See Is What You Get. We think it was our friend Mark Kampe who originally used the old Flip Wilson line to distinguish between embedded- directive and keyboard-command word processors.)

Our friend, play again. Longtime readers will remember Tom as a fellow who keeps prodding us into thinking about problems that end up as columns.

Typically, the way this works is that Tom has an idea, writes a script to implement it that doesn't quite work, sends it to us with a request for help, and we get sucked into working on it.

(Of course, most of Tom's scripts work; if they don't have problems, he doesn't send them to us. What keeps turning the things he sends us into columns is that they're interesting ideas.)

The most recent of these columns evolved out of Tom's need for a watchdog that monitored files for changes.

Beginning with Tom's first-cut shell script, we wrote a replacement in Perl that solved his original problems, then showed how to refine it into an interpreter for a small, but real programming language, atchange, all in about fifty lines of code.

A simple invocation, like

atchange foo date
will print the date every time the file foo changes. This is useful, for example, if you're monitoring a slow process that only provides occasional output.

At the other end of the scale, here's an example atchange program that watches a variety of files and takes different actions when each of those files changes:

# Here's a program for atchange

    HELLO="hello world"  # set a variable
    echo $PS1

/tmp/hello     echo $HELLO         # all one script

    datefn() {      # define a function
      echo the date: $(date)

/tmp/date datefn
    echo -n "$PWD$ "


/tmp/counter   # commands can span multiple lines
    echo $counter
    let counter=counter+1


    echo $CLEARSTR
    let iterator=iterator+1
    echo $iterator | tee /tmp/iterator

    let counter=0
    touch /tmp/counter

(Tom has posted a copy of our original column at If you're in a browsing mood, we encourage you to go up to Tom's home page and take a look at some of the other stuff he's doing.)

Tricks with Netscape
Last month, Tom dropped us a note that builds on atchange.

We're not the only folks Tom talks to, of course. Recently, Stephen Eglen, at the University of Sussex, pointed out to Tom that you can send instructions to a running invocation of Netscape from the command-line.

For example, netscape -remote 'openURL(' will cause the Netscape you're currently running to go to the QMS home page. (If you're not running a Netscape, the command will just print an error message and fail.) The folks at Netscape explain other sorts of commands that you can give Netscape from the command line at

This seemed perfect. Tom reasoned that he could tell atchange to watch an HTML file he was editing in vi; every time it changed, atchange could tell Netscape to redisplay it.

Figure 1 is code that does exactly that.

 1   #!/usr/local/bin/bash
 2   # $Id: eh,v 1.2 1997/03/26 16:10:42 jeff Exp $

 3   # edit an html file while you watch the results in netscape
 4   #    Netscape will refresh every time you write the file

 6   #    - fix the shebang line and the PATH
 7   #    - make sure atchange is installed

 8   PATH=/usr/local/bin:/bin:/usr/bin

 9   # Print usage message and exit
 10  usage() {
 11      ARGV0=${0##*/}
 12      echo "usage: $ARGV0 [-l|-h|-help] filename|filename.html" 1>&2
 13      test "$1" = "long" || exit 1

 14      cat <<-__EOD__ 1>&2

 15       Netscape will display the the indicated html file
 16       and the editor will be invoked on it simultaneously.
 17       Whenever you write out the file in the editor,
 18       Netscape will update.

 19       $ARGV0 will create a template if the named file doesn't exist,
 20       and will add an .html extension to the filename
 21       if you don't type it.

 22       For further information, see
 24       or write Tom Schneider, <>.
 25  __EOD__
 26      exit 1
 27  }

 28  # Make a template html document
 29  html_template() {
 30       cat <<-__EOD__
 31            <html>
 32            <head><title>
 33                 FIXthisTITLE
 34            </title></head>

 35            <body
 36                 bgcolor="#EEFFFA"
 37                 text="#000000"
 38                 link="#CC0000"
 39                 alink="#FF3300"
 40                 vlink="#000055"
 41            >

 42            <center>
 43            <h1>
 44                 FIXthisHEADING
 45            </h1>
 46            </center>

 47            PUTsomethingHERE

 48            </body></html>
 49  __EOD__
 50  }

 51  case $1 in
 52       -l|-h|-help)   shift; usage long ;;
 53  esac
 54  test $# -eq 1 || usage

 55  # Add .html if necessary
 56  html=${1%%.html}.html
 57  if test "${html#/}" = "$html"
 58  then
 59       html=$PWD/$html
 60  fi

 61  test -f $html || html_template > $html

 62  netscape -noraise -remote "openFile($html)"
 63  atchange $html "netscape -noraise -remote 'reload'" &

 64  # Finally, invoke the editor:
 65  ${EDITOR:-vi} $html
 66  kill 0
 67  wait


Let's go through the code a line at a time.

Line 1 is the ``shebang'' (#!) line that tells the system what interpreter to use. We use bash, but another POSIX-conforming shell, like ksh, should work fine, too.

Line 2 is our RCS Id. Yes, we really keep our shell scripts under source code control.

Lines 3-7 are comments, including a comment about how to install the code if you're putting it onto a new system. We try to make our code as portable as we practically can, but there are often problems with portability around the edges, and documenting them in the code helps whoever's trying to get it to run.

Line 8 is safety netting that we put in reflexively. In theory, this line is there to guard against Trojan horses. In practice, it's mostly valuable as a guard against unusual individual PATH-variable settings, and helps us catch cases in which we're accidentally depending on our own, odd PATHs.

Lines 9-27 are a shell function that prints out a usage message. If this were a perl script, we'd just have a simple usage message, and construct a full-blown man page, integrated with the code, using perl's ``pod'' facility, for more complete documentation. (See the perlpod(1) man page for details.) For tiny shell scripts like this, though, we tend to be lazier. In this case, we've created two kinds of usage messages: a typical, UNIX, one-line synopsis of the proper invocation, and a longer help message, which isn't really a man page, but will do until we write one.

If you're not used to shell parameter expansion, line 11 will look mysterious. This statement trims the directory information from $0, the name of the script itself, and put the result in $ARGV0

We could have used basename to do the trimming, but parameter expansion lets you do the same job in the shell itself, without the cost of spawning a new process.

We challenge the reader who wants to learn more about parameter expansion to try to figure out why this statement

: ${PERL5LIB=/usr/local/lib/perl5}
sets the value of PERL5LIB to /usr/local/lib/perl5 if and only if PERL5LIB isn't already assigned a value. (N.B.: the initial colon is important. Don't leave it out!) We use this trick for providing default values inside shell scripts that can be overridden by environment variables.

By the way, as you can see from lines 11, 12, and 19, we never hard-wire the name of the program into the program itself. It's too easy to rename the executable but forget to change it within the code. As an example, this program, which we currently call eh, had at least three different names while we were developing it.

Note also, in lines 12 and 14, that we make sure to send error messages to standard error, not standard out. This is the sort of care most programmers take with their C programs, but often forget to take with their shell scripts.

Lines 28 through 51 encapsulate a template HTML page. We've spent a fair amount of time tinkering with our template, and expect we'll continue to do so as our tastes change. If your taste in web pages is different from ours, here's where you tinker.

For colors (lines 36-40), we follow David Siegel's recommendations, which he discusses in detail at

Line 52 brings us to the main body of the program. We begin by checking for proper invocation, in lines 52-55, and then use parameter expansion again to put the filename into a standard form: after lines 56-61 $html will hold the absolute path of a file whose name ends in .html.

Line 63 checks to see whether the file already exists. If not, it creates the file, using the template we specified earlier. The trick of using || and && to implement simple conditionals is a little confusing at first, but it's a common idiom in shell scripts. We could have written lines 58-61 as the single line

test "${html#/}" = "$html" && html=$PWD/$html
but we left it as four to contrast the two forms for you.

Line 64 tells the Netscape you're currently running to display the current version of the file $html. We follow this immediately with an atchange command, that will tell Netscape to redisplay this file whenever it changes.

Finally, we start up the editor, once again using parameter expansion. This time, we start up vi unless the variable $EDITOR is set in the environment, in which case we use the editor it names. This lets you use ed as your editor if you really want to. (This line is really for the benefit of our boss, Steve, who decided he didn't like vi or emacs, and wrote his own screen editor, se -- Steve's Editor.)

lines 68 and 69 provide us with a gracious exit.

kill 0
kills off the atchange and wait waits for it to finish before the script exits, to avoid creating a zombie.

Last, but not least, line 24 illustrates a small but useful design principle: always point complaints at someone else.

Sow's Ears and Silk Purses

Okay, so we have something that lets us use vi as a wysiwyg HTML editor: so what?

Well, first of all, we've thrown in a lot of little tips, and we hope even readers who don't want eh itself will have picked up a trick or two. If you've been reading this column for a while, you'll know that's our normal approach: create something useful, but make getting there be half the fun.

Second, though, we confess that we use vi all the time. Any flavor of vi: vi, nvi, elvis, vim, viper... you name it.

Yes, we've used a lot of other editors. There are even tasks for which we routinely use (gasp!) emacs, -- especially debugging with gdb -- if only to keep our control, alt, and escape keys from getting lonely.

Still, for garden-variety editing we always seem to come back to vi.

We can rationalize this by saying that it's a standard (POSIX 1003.2), or by arguing that it's small and well integrated into the rest of the UNIX tool set, or by pointing out how beautifully ergonomic its cursor-motion sequences are. Really, we suspect that it's mostly because we've been using it for so long that its commands are wired into our fingers.

This preference is pervasive. Our .profiles contain the line set -o vi, to let our fingers use vi commands to search and edit our shell command histories, and our .emacs files contain the lines (require 'viper) and (setq vip-always t), so that we can use vi commands inside GNUs, when replying to usenet postings.

We write our columns using vi and troff. Naturally, we want to continue to use vi, even when we're building web sites.

We don't even think our preference is unusual. We are, for example, willing to bet that the majority of technical columnists for this magazine also write everything from columns to email with a simple text editor like vi, rather than wysiwyg versions of Microsoft WordPerfect for FrameMaker Windows.

Putting our money where our mouths are, we bet our editors a nickle, hard cash. Lisa and Lisa: Put up, or shut up.

A Side Note.
Several months ago, in our last RS column, we discussed errors in some order-of- magnitude numbers people have been throwing around while discussing IPv6 and competing Internet addressing schemes. We did this by deriving things like the number of protons in the Earth from stuff we learned in high school geometry and chemistry. Later, we were reading Bruce Schneier's Applied Cryptography (second edition, Wiley, 1996, ISBN 0-471-12845-7 or 0-471-11709-9). We discovered that for comparison purposes, Schneier provides a whole list of large numbers for in his table 1.1. His 10**51 atoms in the Earth nicely compares with our order-of-magnitude calculation of 3x10**52 protons in the Earth. Check out the table for some other interesting numbers.

Until next month, then, happy trails.

color bar

Further information about atchange is on the atchange page.

color bar

Small icon for Theory of Molecular Machines: physics,
chemistry, biology, molecular biology, evolutionary theory,
genetic engineering, sequence logos, information theory,
electrical engineering, thermodynamics, statistical
mechanics, hypersphere packing, gumball machines, Maxwell's
Daemon, limits of computers
Schneider Lab
origin: 1997 January 7
updated: 2012 Mar 08 color bar