Command line utilities for the Address Book
February 12, 2007 at 11:45 PM by Dr. Drang
The Mac gives you many scripting language choices. My preferences, from most-liked to least-liked, are
- Perl, at the top of the list because I have a decade of experience with it.
- Ruby, coming on strong as I get more accustomed to object-oriented thinking.
- Bash shell, which I really hate because of its bizarre syntax, especially with assignment statements; but which is often the best choice when a script is nothing more than a few commands strung together.
- AppleScript, programming in which is—for me, at least—nothing more than an exercise in trial and error.
So this post and one I plan to write tomorrow are about how I used my two least-liked scripting languages to put together a couple of useful utilities. I won’t talk about how long it took me to write them, because that would be embarrassing; and I won’t talk about my frustrations, because that would double the length of the post and make it hard for me to get a good night’s sleep.
The first utility is called plainaddress
, and it extracts the name and address information from an Address Book entry and returns it formatted for a letter or an envelope or an address label. For example, if I invoke it from the command line this way
plainaddress John Smith
it will spit back this
John L. Smith
Smith and Associates
1234 Main Street
Suite 222
Centerville, IL 60606
The plainaddress
script itself is just a couple of lines of bash:
#!/bin/bash
osascript ~/bin/plainaddress.scpt "$*"
This is saved in my ~/bin
folder, which is in my $PATH
, and made executable with chmod +x ~/bin/plainaddress
.
The only interesting part of this script is the way it uses the $*
variable to collect all the arguments and quotes them to present a single argument (possibly with spaces) to the plainaddress.scpt
AppleScript.
As you might expect, plainaddress.scpt
is where the real work is done. Here’s the listing:
on searchName(theName)
tell application "Address Book"
set nameWords to every word in theName
set personList to every person whose name¬
contains the first item of nameWords
repeat
if the length of nameWords = 1 then
exit repeat
else
set newPersonList to {}
set nameWords to rest of nameWords
repeat with p in personList
if name of p contains first item of nameWords then
set end of newPersonList to p
end if
end repeat
set personList to newPersonList
end if
end repeat
return the first item of personList
end tell
end searchName
on fullName(p)
tell application "Address Book" to set fn to name of p
set titles to {"Mr.", "Ms.", "Miss", "Mrs.", "Dr."}
set AppleScript's text item delimiters to " "
set nameWords to text items of fn
if first item of nameWords is in titles then
set nameWords to rest of nameWords
end if
return nameWords as string
end fullName
on mailingAddress(p)
tell application "Address Book" to set addr to¬
formatted address of address of p as string
set AppleScript's text item delimiters to "
"
set addrLines to text items of addr
if last item of addrLines is "USA" then
set addrLines to reverse of rest of reverse of addrLines
end if
return addrLines as string
end mailingAddress
on run argv
set nameaddr to ""
try
set contact to searchName(item 1 of argv)
set nameaddr to fullName(contact) & "
"
tell application "Address Book"
if exists (organization of contact) then
set nameaddr to nameaddr & (organization of contact) & "
"
end if
end tell
set nameaddr to nameaddr & mailingAddress(contact)
return nameaddr
end try
end run
This was written in the Script Editor and saved as a script in my ~/bin
folder.
The searchName
function is my attempt to create an AppleScript function that mimics the way the Address Book search field works. If you type “John Smith” into the Address Book search field, the entry for John L. Smith will be found—you don’t have to include the middle initial in your search. But, as far as I can tell, there is no comparable AppleScript command in the Address Book dictionary. If you try
get every person whose name contains "John Smith"
you won’t find John L. Smith because of the missing middle initial.
How searchName
gets around this is by
- splitting the search string into words (substrings separated by spaces);
- getting the list of people whose name contains the first word;
- refining that list of people, keeping only those whose name also contains the second word; and
- repeating step 3 for the subsequent words in the search string.
- returning the first person in the refined list.
If there’s only one word in the search string, steps 3 and 4 are skipped. If there are only two words in the search string, step 4 is skipped. If more than one entry matches the search, only the first entry is returned; this makes the subsequent code easier to write and has never posed a problem for me—I know my Address Book well enough to know how much of a name is needed to insure a unique result.
The on run
portion of the script uses searchName
to get the Address Book entry for the person we want, then plucks out the address information and formats it. I think it pretty much speaks for itself. The only comment I have is that I hate how the Script Editor changed every "\n"
I typed into a literal newline; it really messes up the indentation.
Although they were a pain to write (OK, I said I wasn’t going to talk about that), these scripts are a pleasure to use, and I use them all the time. I especially like that I don’t have to quote the search string if it contains spaces. It’s so much more natural to write
plainaddress John Smith
than
plainaddress "John Smith"
Actually, it’s even more natural to write
plainaddress john smith
which works because AppleScript’s string comparison is case-insensitive.
You may be wondering why I didn’t wrap the AppleScript into the shell script by using a here document, creating only one script instead of two. It’s because I’ve never gotten here documents to work in shell scripts the way I’ve gotten them to work in Perl. So, after beating my head against that particular wall for a while (right, said I wasn’t going to talk about that), I decided to wimp out and use two files. Sue me.
Update
After looking over the AppleScript, I decided I could make it easier to read by factoring out the parts that get the contact’s name and address. The script you see now reflects those changes. Both fullName
and mailingAddress
use the text item delimiters
property to split a string into a list and then construct a string from a list. Between the splitting and reconstructing, the lists are manipulated using some of the ideas AppleScript borrowed from Lisp; not surprisingly, Apple chose to use the names “first” and “rest” instead of “car” and “cdr.”