Saving Persistent Values

A frequent AppleScript task involves saving values from one running of a script to the next. There are a number of options including:

  • A text file
  • A script library
  • A plist file

The text-file option is fairly straightforward, and the script-library alternative is described here. There are several approaches that can be used to save values to a plist file.

The first approach is to use NSUserDefaults, which is the fastest and simplest alternative. It will return a default value and create the plist file if it doesn’t exist. This approach works with text, integers, booleans, lists, records, and dates (and their ASObjC equivalents). The following example saves a list.

use framework "Foundation"
use scripting additions

--the preference value is retrieved at the beginning of the script
set thePreference to getPreference()

--the body of the script goes here and typically sets a preference value
set preferenceValue to {"aa", 3}

--the preference value is saved at the end of script
writePreference(preferenceValue)

on getPreference()
	set theDefaults to current application's NSUserDefaults's alloc()'s initWithSuiteName:"com.peavine.test"
	theDefaults's registerDefaults:{keyOne:{"", 0}} --set default value as desired
	return keyOne of theDefaults as list
end getPreference

on writePreference(theValue)
	set theDefaults to current application's NSUserDefaults's alloc()'s initWithSuiteName:"com.peavine.test"
	set keyOne of theDefaults to theValue
end writePreference

Another approach is to use System Events, although it’s a bit complicated and marginally slower. It will work with the same values as NSUserDefaults.

--write new or overwrite existing key and value (plist must exist)
set thePlist to (path to preferences folder as text) & "com.peavine.test.plist"
set theKey to "keyOne"
set theValue to {"a", 3}
tell application "System Events" to make new property list item at property list file thePlist with properties {kind:list, name:theKey, value:theValue}

--write value of existing key
set thePlist to (path to preferences folder as text) & "com.peavine.test.plist"
set theKey to "keyOne"
set theValue to {"a", 3}
tell application "System Events" to set value of property list item theKey of property list file thePlist to theValue

--read value of a key
set thePlist to (path to preferences folder as text) & "com.peavine.test.plist"
set theKey to "keyOne"
tell application "System Events" to set theValue to value of property list item theKey of property list file thePlist

--create new or overwrite existing plist
set thePlist to (path to preferences folder as text) & "com.peavine.test.plist"
tell application "System Events"
	set theDictionary to make new property list item with properties {kind:record}
	make new property list file with properties {contents:theDictionary, name:thePlist}
end tell

A final approach is to use the defaults shell command. This is slower yet and is pretty much restricted to text, integers, and booleans. However, it is useful with a shortcut.

--write and read a key-value pair to a plist file
--the plist file will be created if it doesn't exist
--the key needs to be enclosed in double quotation marks if it contains a space
--the value must always be enclosed in single quotation marks

--write and read a string
set thePlist to quoted form of (POSIX path of (path to preferences folder as text) & "com.peavine.test")
set theKey to "keyOne"
set theValue to quoted form of "aa aa"
do shell script "defaults write " & thePlist & space & theKey & " -string " & theValue
set keyOneValue to do shell script "defaults read " & thePlist & space & theKey -->"aa aa"

--write and read an integer
set thePlist to quoted form of (POSIX path of (path to preferences folder as text) & "com.peavine.test")
set theKey to "keyTwo"
set theValue to quoted form of "3"
do shell script "defaults write " & thePlist & space & theKey & " -integer " & theValue
set keyTwoValue to (do shell script "defaults read " & thePlist & space & theKey) as integer -->3

--write and read a boolean
set thePlist to quoted form of (POSIX path of (path to preferences folder as text) & "com.peavine.test")
set theKey to "keyThree"
set theValue to quoted form of "true"
do shell script "defaults write " & thePlist & space & theKey & " -boolean " & theValue
set keyThreeValue to (do shell script "defaults read " & thePlist & space & theKey) as integer as boolean -->true
3 Likes

I was curious how the System Events option might be implemented in an actual script, and the following is an example. This assumes that the plist values will be read at the beginning of the script and, when required, written at the end of the script.

This seems a lot of work to save a few values. However, in my testing, reading and writing the plist settings only took 3 and 4 milliseconds, respectively. Fortunately, there are many options, and users can decide which works best in their script.

set thePlist to (path to preferences folder as text) & "com.peavine.test.plist" --set to desired value

--read value of keys
--prompt to create plist file if the file or a setting in the file is not found
try
	tell application "System Events" to tell property list file thePlist
		set keyOneValue to value of property list item "keyOne"
		set keyTwoValue to value of property list item "keyTwo"
		set keyThreeValue to value of property list item "keyThree"
	end tell
on error
	display dialog "The script's plist file or a setting in the plist file could not be found. Do you wish to create a new plist file with default settings?"
	createNewPlist(thePlist)
	error number -128
end try

--the main body of the script

--write values of keys as desired
tell application "System Events" to tell property list file thePlist
	set value of property list item "keyOne" to "aa"
	set value of property list item "keyTwo" to 3
	set value of property list item "keyThree" to {"aa", 3}
end tell

--create plist with default values
--set defaults to desired values
on createNewPlist(thePlist)
	tell application "System Events"
		set theDictionary to make new property list item with properties {kind:record}
		set thePlist to make new property list file with properties {contents:theDictionary, name:thePlist}
		tell thePlist
			make new property list item with properties {kind:string, name:"keyOne", value:""}
			make new property list item with properties {kind:integer, name:"keyTwo", value:0}
			make new property list item with properties {kind:list, name:"keyThree", value:{}}
		end tell
	end tell
end createNewPlist

The following is a screenshot of the plist file created by the above script:

2 Likes

I use the system events/plist approach.

In most of my scripts I store the global data in records. I find that I can easily copy or paste entire records into a plist.

Caveat: it took me a long time to realize that when I copy an entire record from a Plist to some record defined and used by a script, all references to elements of the record are “half broken” - references in place before the copy operation no longer refer to elements of the record, however they still refer to some invisible element, which can be copied and changed.

I don’t understand why people don’t just save the list or record out to a text file,
and then read it back in? It is very short on code and works great.

You only need to use something like plists if you need it to be read by other programs, or you want it in human readable form.

Robert. Just so people will have a working example, I’ve included one below. Are you using a different approach? My example needs error correction.

set theFile to (path to documents folder as text) & "Script Preferences.txt" --set to desired value
set theString to "peavine"
set theDate to (current date)
set theList to {"a", "b"}
set theRecord to {name:"peavine", home:"Prescott"}
set theData to {theString, theDate, theList, theRecord}

--write data to text file
set openedFile to (open for access file theFile with write permission)
set eof openedFile to 0
write theData to openedFile as list
close access openedFile

--read data from text file
set theData to read file theFile as list

--just a test
set datePreference to item 2 of theData
set dateString to date string of datePreference -->"Friday, January 17, 2025"
set recordPreference to item 4 of theData
set theName to name of recordPreference -->"peavine"

The timing result for the above script is 2 milliseconds, and the timing result for the read section is 1 millisecond, which is plenty fast.

Not sure if its needed by you might want to add “as list” to the end of the write command, just incase it tries to convert to text.

write theData to openedFile as list

I have many scripts which use my ‘Preferences’ library. It saves all data to a prefs file in the users Preferences Folder in the User’s Library folder. It always saves the prefs as a list so it can handle multiples data values.

1 Like

Interesting idea.
Reading your suggestions I thought of a script I wrote a long time ago.
It involved rewriting the text of a SE document.
Well, you can save a property after a run in the document.
I tested to see if it worked, and it does.
I am posting the example below.
Try changing the property and check what happens.
I don’t know how deep this method can go, but I thought it is interesting.

property nome : “John”

display dialog "Hello " & nome giving up after 2

set nome to text returned of (display dialog “Enter your name:” default answer nome giving up after 5)

display dialog "Hello " & nome giving up after 5

set t to text of document 1

display dialog "Hello " & t giving up after 6

set tw to word 3 of t

tid(tw)

set text of document 1 to (text item 1 of t) & nome & (text item 2 of t)

on tid(x)

set AppleScript’s text item delimiters to x

end tid