Export All Contacts with Phone Numbers, One per line?

I am trying to find a quick & easy way to take a phone number and see if anyone in my Contacts app has that phone number. If yes, I would like to output the name or names (could be 1 or more individuals, could be a company).

I was trying to do this with sqlite and then remembered that AppleScript has (or at least had) good ways of working with contacts. I’m not sure if that is still true or not.

Looking around for examples, it seems that potential problems are:

  1. Some phones may be shared with multiple people

  2. People may have more than one phone number associated with their name

  3. Although Contacts app may display phone numbers consistently, that does not mean they are consistent in the DB. For example, any of these formats could be found:

(555) 321-4567
+1 (555) 321-4567

Any help would be appreciated!

Maybe the code below will help with your project. —Stan C.

	Find Phone Numbers in Contacts
	Written by Stan Cleveland
	Script dependencies:
		macOS X Mojave v10.14.6 or higher
	1.	Possible phone number formats:
			"+1 (458) 204-4219"
			"(503) 799-6280"
			Other shorter messaging numbers
	2.	There apears to be two different space characters used in Contacts.
	Version history:
	v1r0  (2021/07/20) -- Initial version. 

-- define search criteria
set phoneNumberToFind to "503-222-5566"

----------------------------------     MAIN SCRIPT     ----------------------------------
-- generate normalized list of phone number information
tell application "Contacts"
	set {pNames, pLabels, pNumbers} to ¬
		{name, label of phones, value of phones} of every person
end tell
-- normalize phone numbers and compile a number cross-reference
set phoneRecords to {}
repeat with i from 1 to (count pNames)
	set thisName to item i of pNames
	set labelsList to item i of pLabels
	set numbersList to item i of pNumbers
	repeat with j from 1 to (count numbersList)
		set thisLabel to item j of numbersList
		set thisNumber to normalize(item j of numbersList)
		set end of phoneRecords to thisNumber & "_" & thisLabel & "_" & thisName
	end repeat -- with j from 1 to (count labelsList)
end repeat -- with i from 1 to (count pNames)
-- cycle through phone information, looking for target number
set foundRecords to {}
repeat with i from 2 to (count phoneRecords)
	set thisRecord to item i of phoneRecords
	if thisRecord starts with phoneNumberToFind then
		-- break record into relevant parts
		set {phoneNumber, phoneType, phoneName} to text items of thisRecord
		set end of foundRecords to {number:phoneNumber ¬
			, type:phoneType, name:phoneName}
	end if -- thisRecord starts with targetNumber
end repeat -- with i from 2 to (count phoneRecords)
return foundRecords

-----------------------------------     HANDLERS     -----------------------------------
on normalize(phoneNumber)
	tid({"+1 ", "+1 ", "+1", "(", ") ", ") ", "-", ".", " ", " "})
	set parts to text items of phoneNumber
	set keeperParts to {}
	repeat with i from 1 to (count parts)
		set thisPart to item i of parts
		if thisPart is in {"1", " ", ""} then
			-- ignore it
			set end of keeperParts to thisPart
		end if
	end repeat -- with i from 1 to (count parts)
	if (count keeperParts) is 1 then
		if (length of item 1 of keeperParts) is 10 then
			set thisPart to item 1 of keeperParts
			set fixedNumber to text 1 thru 3 of thisPart & "-" & ¬
				text 4 thru 6 of thisPart & "-" & text 7 thru 10 of thisPart
		else -- (length of item 1 of keeperParts) is not 10
			set fixedNumber to keeperParts as text
		end if -- (length of item 1 of keeperParts) is not 10
	else -- (count keeperParts) > 1
		set fixedNumber to keeperParts as text
	end if -- (count keeperParts) > 1
	return fixedNumber
end normalize

on tid(d)
	set my text item delimiters to d
end tid

Oops. Change this line:

set thisLabel to item j of numbersList

To this:

set thisLabel to item j of labelsList
1 Like

Wow! Thank you! I will try this out later.

One probably-dumb question: would it be difficult to this in a shell script or something where I would give the phone number as an ‘argument’ to the script?

Thanks again!

Hi TJ,

Almost anything is possible, but I leave it to you to adjust things to meet your needs. I posted a general solution to get you started, but am not in a position to take on custom coding. Learning AppleScript isn’t easy, but is quite possible and well worth the time and effort, you’ll find.

Stan C.

Hey TJ,

I’ve taken a bit of a different tack than Stan and gone for pure speed. This script takes about 0.4 seconds to run on my fairly old hardware.

  • I’ve not normalized all the phone number formats – just a couple – more is an exercise for the user.

  • I’ve not massaged the input number – another exercise for the user.

  • Look for this line to test, and feed it a valid number in the given format from your contacts.

      set findPattern to "(000) 111-2222"
  • I’ve emplaced code to use Keyboard Maestro as the input/run mechanism if desired.

  • If one or more numbers are found a choose-from dialog displays all found records.

  • If nothing is found a system notification is run to indicate same.

If you need help fleshing-out the phone number normalizing code let me know.


# Auth: Christopher Stone
# dCre: 2021/07/20 23:37
# dMod: 2021/07/20 23:43
# Appl: Contacts
# Task: Check to see if a given phone number is in your contacts.
# Libs: None
# Osax: None
# Tags: @Applescript, @Script, @ASObjC, @Contacts
use AppleScript version "2.4" --» Yosemite or later
use framework "Foundation"
use scripting additions

tell application "Contacts"
   if not running then run
end tell


--» Get phone number to search for from Keyboard Maestro
set kmInstance to system attribute "KMINSTANCE"
tell application "Keyboard Maestro Engine"
   set findPattern to getvariable "local_FindPattern" instance kmInstance
end tell


--» •• Phone Number to search for testing if not available from Keyboard Maestro. ••
set findPattern to "(000) 111-2222"

--» Escape parenthesis in phone number for regular expression.
set findPattern to my cngStr:"([()])" intoString:"\\\\$1" inString:findPattern


--» Generate flat list of contact records.
tell application "Contacts"
   set {nameList, labelList, phoneNumList} to {name, label of phones, value of phones} of every person
end tell

set collatedPhoneNumList to {}

set AppleScript's text item delimiters to " · "

repeat with ndx1 from 1 to length of labelList
   set tempLabelList to item ndx1 of labelList
   set tempPhoneNumList to item ndx1 of phoneNumList
   set tempRecord to {}
   repeat with ndx2 from 1 to length of tempLabelList
      set end of tempRecord to item ndx2 of tempLabelList
      set end of tempRecord to item ndx2 of tempPhoneNumList
   end repeat
   set itemRef to (a reference to item ndx1 of phoneNumList)
   set itemRef's contents to (item ndx1 of nameList) & " · " & (tempRecord as text)
end repeat

set AppleScript's text item delimiters to linefeed

set phoneNumList to phoneNumList as text

set phoneNumList to my cngStr:"\\t" intoString:" · " inString:phoneNumList

--» Normalize Phone Number Formats.
set phoneNumList to my cngStr:"(\\d{3})(\\d{3})(\\d{4})" intoString:"($1) $2-$3" inString:phoneNumList
set phoneNumList to my cngStr:"(\\d{3})-(\\d{3})-(\\d{4})\\b" intoString:"($1) $2-$3" inString:phoneNumList

set findPattern to ("(?m)^.*" & findPattern & ".*$")

set foundRecord to my extractUsingPattern:findPattern fromString:phoneNumList resultTemplate:"$0"

if foundRecord ≠ {} then
   set AppleScript's text item delimiters to linefeed
   choose from list foundRecord with title "Contacts Search"
   display notification "Phone Number NOT Found!" with title "Search Contacts" subtitle "···························" sound name "Tink"
end if

on cngStr:findString intoString:replaceString inString:dataString
   set anNSString to current application's NSString's stringWithString:dataString
   set dataString to (anNSString's ¬
      stringByReplacingOccurrencesOfString:findString withString:replaceString ¬
         options:(current application's NSRegularExpressionSearch) range:{0, length of dataString}) as text
end cngStr:intoString:inString:
on extractUsingPattern:thePattern fromString:theString resultTemplate:templateStr
   set theString to current application's NSString's stringWithString:theString
   set theRegEx to current application's NSRegularExpression's regularExpressionWithPattern:thePattern options:0 |error|:(missing value)
   set theFinds to theRegEx's matchesInString:theString options:0 range:{0, theString's |length|()}
   set theResult to current application's NSMutableArray's array()
   repeat with aFind in theFinds
      set foundString to (theRegEx's replacementStringForResult:aFind inString:theString |offset|:0 template:templateStr)
      (theResult's addObject:foundString)
   end repeat
   return theResult as list
end extractUsingPattern:fromString:resultTemplate:
1 Like

Thank you!

An abundance of riches here!

1 Like