reading/writing array with defaults read/write?

I am working on an application that stores its settings in a plist using “defaults read” and “defaults write” in a do shell script. This has been working great but now I need to store and retrieve an array of values.

Searching this forum I found that this should be possible, I also found other examples that show the array flag in use in the defaults command, which sent me looking at the man page for the defaults command.

I guess my question is now how do I format a list so that I can pass it through to defaults and also read an array into a list from defaults?

set someList to {"entry 1", "entry 2"}
do shell script "defaults write com.blah someEntry -array " & someList

EDIT - Okay the read mechanism is really really annoying when it returns an array. This will currently only work when your elements have a space in them.

set writeList to {"entry 3", "entry 4"}

writeArray("com.testscript.wplate", "testArray", writeList)
set someList to readArray("com.testscript.wplate", "testArray")


on writeArray(theDomain, theKey, theList)
	set theArrayList to ""
	repeat with anItem in theList
		set theArrayList to theArrayList & "'" & anItem & "'" & space
	end repeat
	set theArrayList to text 1 thru -2 of theArrayList
	set command to "/usr/bin/defaults write " & theDomain & space & theKey & " -array " & theArrayList
	do shell script command
end writeArray

on readArray(theDomain, theKey)
	set command to "/usr/bin/defaults read " & theDomain & space & theKey
	set {tid, text item delimiters} to {text item delimiters, "\""}
	set rawList to text items of (do shell script command)
	set varList to {}
	repeat with i from 2 to (count rawList) by 2
		set end of varList to item i of rawList
	end repeat
	set text item delimiters to tid
	return varList
end readArray

Thank you very much!

Thank you, while I’m afraid this example worked by itself I’m not having any luck with different (real world) values in the array.

This screenshot shows the values rawList got from the do shell script command:
http://www.automaticduck.com/screenshots/rawListwithentry3entry4.jpg

When I put some other values into the array, like “NONAME” and “NO NAME” the rawlist variable appears like this:
http://www.automaticduck.com/screenshots/rawListwithmyvalues.jpg

Resultingly when the script counts by two later on I’m left with just one item in my list.

I’m sure I’ve done something wrong but I I’ve stepped through in debugging mode like ten times and I’m not seeing my mistake. Can anyone else point out what I’ve done wrong?

How about this?

on readArray(theDomain, theKey)
	set command to "/usr/bin/defaults read " & theDomain & space & theKey
	set arrayItems to text 2 thru -2 of (do shell script command) -- Lose the enclosing brackets.
	
	set astid to AppleScript's text item delimiters
	set AppleScript's text item delimiters to "\""
	set unquotedQuoted to arrayItems's text items
	-- 'unquotedQuoted is a list whose even-numbered items (if any) were quoted in the returned array text.
	-- The odd-numbered items are either empty texts, comma-spaces, or blocks of unquoted array text.
	
	set outList to {}
	set AppleScript's text item delimiters to ", "
	repeat with i from 1 to (count unquotedQuoted)
		if (i mod 2 is 1) then -- Odd number: parse out unquoted array items.
			set unquotedItems to text items of item i of unquotedQuoted
			repeat with j from 1 to (count unquotedItems)
				set thisItem to item j of unquotedItems
				if ((count thisItem) > 0) then set end of outList to thisItem
			end repeat
		else -- Even number: this was a quoted array item.
			set end of outList to item i of unquotedQuoted
		end if
	end repeat
	set AppleScript's text item delimiters to astid
	
	return outList
end readArray

Nigel, I didn’t really look at the code yet, but here was my quick test using your read function

set writeList to {"entry 3", "entry 4", "entry5"}

writeArray("com.testscript.wplate", "testArray", writeList)
set someList to readArray("com.testscript.wplate", "testArray")

Here is my result

somelist
item 1 "\r "
item 2 “entry 3”
item 3 ",\r "
item 4 “entry 4”
item 5 “,\r entry5\r”

The issue is with a value that does not have spaces in the entry. Nigel and I both appear to be looking into a solution that isn’t ridiculous :smiley:

Okay took another stab at this and here is what I came up with. Give this a try :slight_smile:

set writeList to {"entry 3", "entry4", "entry 8", "entry5", "entry 6 7", "entry 7 8 9", "entry"}

writeArray("com.testscript.wplate", "testArray", writeList)
set someList to readArray("com.testscript.wplate", "testArray")


on writeArray(theDomain, theKey, theList)
	set theArrayList to ""
	repeat with anItem in theList
		set theArrayList to theArrayList & "'" & anItem & "'" & space
	end repeat
	set theArrayList to text 1 thru -2 of theArrayList
	set command to "/usr/bin/defaults write " & theDomain & space & theKey & " -array " & theArrayList
	do shell script command
end writeArray

on readArray(theDomain, theKey)
	set command to "/usr/bin/defaults read " & theDomain & space & theKey
	set rawList to paragraphs of (text 3 thru -3 of (do shell script command))
	set outList to {}
	repeat with i from 1 to (count rawList) - 1
		set tmpItem to text 5 thru -2 of item i of rawList
		if tmpItem's character 1 is "\"" then set tmpItem to text 2 thru -2 of tmpItem
		set end of outList to tmpItem
	end repeat
	set tmpItem to text 5 thru -1 of item -1 of rawList
	if tmpItem's character 1 is "\"" then set tmpItem to text 2 thru -2 of tmpItem
	set end of outList to tmpItem
	return outList
end readArray

I have an AppleScript Studio project that does exactly what you are trying to do, save a table basically that can be read in from an external file and edited and then saved back out.

I did this by saving the data in an XML file rather than the pList file. If you have any experience with XML I think that is the best way to go about it. I use System Events to read the XML data in and then use the free Late Night Software XML scripting addition to write out the new XML file at the end.

http://www.latenightsw.com/freeware/XMLTools2/index.html

If you are interested in that approach I can pull out just the relevant code and post.

Thank you, Matt-Boy for the XML suggestion, I probably would have gone that route had it not been easy to read and write basic settings using defaults. Then when I realized I needed an array this thread started.

James Nierodzik’s last example does seem to do what I need it to do (at least for now), so I’ll give it a go.

Thank you everyone.

Glad that worked out for you!

I’m interested :smiley:

Strange. It works on both my Jaguar and Tiger machines, whereas your revised script in post #8 gives this result:

Obviously arrays are returned in a different format on your system. :confused:

Well I’m going to have test both of the solutions tomorrow on a Tiger machine at work to see if the result is different that on my Leopard machine.

Here’s the basics, James:

XML file for doing find and replace of colors in InDesign looks like this:

<?xml version="1.0"?> Black Variable Black Variable Text Black White Variable Paper

And the Applescript. In between the reading in of the values and the writing out other stuff happens in my XCode project, but this is just the read and write routines with the sorting routine that keeps the list in alphabetical order.

set configurationPath to path to desktop as text

set XMLfile to configurationPath & "Colors_Find_Replace.xml"
set colorList to {}
set theColorListTemp4Sorting to {}
tell application "System Events"
	tell XML element 1 of contents of XML file XMLfile --tell <colors> block
		set colorListTotal to (count of XML elements)
		--Pull find and replace color values out of XML file and create a record list.
		repeat with thisElement from 1 to colorListTotal
			set colorListTempFind to (value of (XML elements whose name is "findColor") of XML element thisElement) as string
			set colorListTempReplace to (value of (XML elements whose name is "replaceColor") of XML element thisElement) as string
			set theColorListTemp4Sorting to theColorListTemp4Sorting & {{findColorValue:colorListTempFind, replaceColorValue:colorListTempReplace}}
		end repeat
		--Sort list alphabetically by find color to create the final colorList
		set colorList to my bubbleSortPairedList(theColorListTemp4Sorting)
	end tell
end tell


--Write out new XML file with updated color list
set theFinalXMLParamsList to {}
repeat with thisColorRecord in colorList
	set theFinalXMLParamsList to theFinalXMLParamsList & {{class:XML element, XML tag:"color", XML contents:{{class:XML element, XML tag:"findColor", XML contents:(findColorValue of thisColorRecord)}, {class:XML element, XML tag:"replaceColor", XML contents:(replaceColorValue of thisColorRecord)}}}}
end repeat
--Set up the parameters for the XML Tools "generate XML" function		
set theXML to {class:XML element, XML tag:"colors", XML contents:theFinalXMLParamsList}
--Generate the new XML and write it over the existing file
generate XML theXML saving as file (configurationPath & "Colors_Find_Replace.xml") without pretty printing


on bubbleSortPairedList(theList2Sort)
	repeat with i from length of theList2Sort to 2 by -1
		repeat with j from 1 to i - 1
			tell theList2Sort
				if ((item j)'s findColorValue) > ((item (j + 1))'s findColorValue) then
					set {item j, item (j + 1)} to {item (j + 1), item j} -- swap
				end if
			end tell
		end repeat
	end repeat
	return theList2Sort
end bubbleSortPairedList

IT LIVES!! Apologies for being annoying.

I got this bit to work by injecting a custom delimiter (“%%”) around each item. In my case I’m not doing any array mangling, only additions to, but so long as your delimiter is not contained in your data you should be OK.

set the_dom to "fax.spammers"
set the_key to "junk_faxes"
set junk_faxes to read_array(the_dom, the_key)

tell application "Page Sender Fax Center"
	try
		set this_folder to selection
		set this_fax to selection of this_folder as list
		if this_fax is {} then
			error
		end if
	on error
		display dialog "You must first select a fax in order to add its sender to the spammer list." with icon 2
		return
	end try
	set this_name to fax name of item 1 of this_fax as string
	if this_name is not "" then
		if this_name is not in junk_faxes then
			if the button returned of (display dialog "Add \"" & this_name & "\" to the list of  spammers?" with icon 1) is "OK" then
				do shell script "defaults write " & the_dom & " " & the_key & " -array-add '%%" & this_name & "%%'"
			end if
		else
			display dialog "Sender \"" & this_name & "\" is already in the list." with icon 2
		end if
	else
		display dialog "Cannot add a blank sender to the list." with icon 0
	end if
end tell

on read_array(the_dom, the_key)
	set {tid, text item delimiters} to {text item delimiters, "%%"}
	set read_plist to text items of (do shell script "defaults read " & the_dom & " " & the_key)
	set str2array to {}
	repeat with i from 2 to (count read_plist) by 2
		set this_item to item i of read_plist
		set end of str2array to this_item
	end repeat
	set text item delimiters to tid
	return str2array
end read_array

In case anyone is wondering: This is to add fax senders to a list of spammers. Page Sender (3) can run an AS on receiving a fax and use that to alter the “name” of the fax to indicate a hit. The above is separate and runs manually on a single fax selected in Page Senders window. It does not check for the existence of the delimiter in the data, though that would probably be a good idea…