October 30, 2006

CLI Magic: Command-line contact management

Author: Michael Stutz

There's an ancient Unix practice of keeping a system-wide phone directory in /usr/share/ with one-line entries containing name, location, and number, and a shell script named something like phone or tel that calls grep to output lines that match whatever arguments you give. You can improve on that method to create a personal contact manager with surprising speed and power.

My requirements for an address book and contact manager are simple. I just want to be able to search and view records whenever I need them, from the command line and a text editor. I don't want to have to start up one of the many standalone applications for this purpose, which all have their own sets of dependencies, file formats, and editing commands. And since I work on a single system and share the data with no one, rigging up a home LDAP (Lightweight Directory Access Protocol) setup is overkill.

Furthermore, this contact manager should be usable whether I happen to be in X or at a console, and perfectly accessible over a slow text-only network line. It should let me cut and paste addresses and contact information from the Web, email, and any other application I might run. There should be no forms to have to complete for each record, and no concept of "required fields" -- each record should be able to contain as much, or as little, information as I happen to have.

With a plain-text file and a few handy tools like awk, you can easily accomplish all of the above.

The contacts file

The first step is to make a new file (call it something like "contacts") and begin adding records to it in your favorite text editor.

Records have to be delimited somehow; I use ### on a line by itself. Format the records themselves however you like, with name and address and whatever information you have. I used to keep a completely free-form contacts file, so that each record contained completely unformatted data in whatever way that I happened to get it, but this practice quickly shows its limitations -- I've found that it helps immensely to label certain fields, such as telephone number and email address. I use this format:

NameAddress
Phone: phone number
Fax: fax number
Email: email address
Comments: additional information

As an example, a few records might look like this:

Acme Industries, Inc.
4211 E Broadway
New York, NY 10026
Phone: (212) 555-1032
Fax: (212) 555-1038
Email: acme@example.net

###

capri pizza
Phone: 555-8250

###

jane smith
Phone: 555-3104
Email: jsmith@example.nyu.edu
Comments: friend of susan's

Searching, browsing, and exporting records

You can search and browse the contacts file in any text editor, of course, but from the command line the tools of the hour are grep in all its variants and awk.

fgrep outputs single lines of the file that match a string you give, and is good for when you just want to see if you have such-and-such a record in your file. Use the -i option to do a case-insensitive search -- for instance, here's how to see if you have contact information for Acme Industries:

$ fgrep -i 'acme industries' contacts
Acme Industries, Inc.
$

The output gave the name -- but you want the phone number too. Output the search with a few lines after the match with the -A option:

$ fgrep -i -A5 'acme industries' contacts
Acme Industries, Inc.
4211 E Broadway
New York, NY 10026
Phone: (212) 555-1032
Fax: (212) 555-1038
Email: acme@example.net
$

And here's where using labels really pays off. When you need, say, all the email addresses that have "nyu.edu" in them, you can find them with a plain grep command:

$ grep '^Email:' contacts | fgrep 'nyu.edu'

Harvesting the actual addresses themselves is also a trivial matter:

$ grep '^Email:' contacts|egrep -o '[^ ]+$'

You can use awk to output entire records containing a particular match. The simplest way is to change the awk record separator, RS, to ### and then enclose the pattern to match in slashes. For example, here's how to export all records containing the string "acme" somewhere in the record:

$ awk 'BEGIN { RS = "###" } /acme/' contacts
Acme Industries, Inc.
4211 E Broadway
New York, NY 10026
Phone: (212) 555-1032
Fax: (212) 555-1038
Email: acme@example.net

$

You can put the command in a script called address that takes a search string as an argument:

#!/bin/sh
awk 'BEGIN { RS = "###" } /'$*'/' ~/contacts

Then you'll get all of the records containing the string you search for, separated by newlines, when you call address:

address smith
address nyu.edu
address 90028

Because the file has labels, you can limit your search to them. For example, you can search for all email addresses that have "smith" in them, and output the entire records:


$ awk 'BEGIN { RS = "###"; FS = "Email: " } ($2 ~ "smith") { print $0 }' contacts

You can use the same awk pattern to do any number of things. For instance, in conjunction with the grep examples above you can output all the email addresses in records that have "friend" in the comment field:


$ awk 'BEGIN { RS = "###"; FS = "Comments: " } ($2 ~ "friend") { print }' contacts | grep '^Email:' | egrep -o '[^ ]+$'

Adding and importing records

Adding records to your contacts file is easy. The file doesn't need to be sorted, so append new records by either editing the file in a text editor or using redirection on the command line:

$ cat >> contacts

Rarely do I actually type out any new contact information myself -- that only happens when I'm transcribing something from paper, or when I'm getting a number from someone on the phone. Nine times out of 10 I'm just cutting and pasting the text from the Web or email into an editor window that has the contacts file open. It's painless and fast -- there are no forms to have to fill out for each part of the record. But you have to keep two things in mind: separate the records with hash marks, and insert labels for numbers, email, and comment fields, if you want to use them.

If you already have a set of address records formatted some other way, awk can import it so that it's in the right format.

Let's say you have a file named address.txt where all the records are kept one to a line in this common format:

lastname,firstname,address,city,state,zip,phone,email

Here's an awk one-liner to take that input and spit it out into the bottom of the contacts file, in just the right format:


$ awk 'BEGIN { FS = "," } { print "\n###\n\n" $2, $1, "\n" $3 "\n" $4 ", " $5, $6, "\nPhone: " $7, "\nEmail: " $8 }' address.txt >> contacts

Click Here!