Thursday, January 04, 2007

Use defaults to Manipulate Any Plist

I assume everyone knows about the defaults(1) command, and that it can be used from the command line to manipulate application's preferences. But what some folks may not know is that it can also be used to muck with almost any plist file. This can be very useful when tearing through plists from the command line or in a shell script.

For example, I can display TextEdit's CFBundleIdentifier with the following command.

$ defaults read /Applications/TextEdit.app/Contents/Info CFBundleIdentifier
com.apple.TextEdit

(Notice that I don't specify the .plist extension when referencing the file.)

Or I could see the services that TextEdit provides with this invocation.

$ defaults read /Applications/TextEdit.app/Contents/Info NSServices
(
{
NSMenuItem = {default = "TextEdit/New Window Containing Selection"; };
NSMessage = openSelection;
NSPortName = TextEdit;
NSSendTypes = (NSRTFDPboardType, NSRTFPboardType, NSStringPboardType);
},
{
NSMenuItem = {default = "TextEdit/Open Selected File"; };
NSMessage = openFile;
NSPortName = TextEdit;
NSSendTypes = (NSStringPboardType);
}
)


Not only can you read the plists, but you can also write to them using the normal defaults write /path/to/file key value technique.

However, I do have one question myself about this. In the TextEdit examples above, is there a way for me to specify that I want to read just the value of, say, the NSSendTypes key, which is in a dictionary, that is in an array, which is itself the value of the NSServices key? I'd love to be able to do this, but I'm not sure of the syntax (or if it's even possible).

Oh, and also take note of this warning from the man page.

WARNING: The defaults command will be changed in an upcoming
major release to only operate on preferences domains. General
plist manipulation utilities will be folded into a different
command-line program.

16 comments:

Anonymous said...

Regarding advanced queries: Maybe you could convert the plist to xml format (using plutil) and then use XPath on the result?

Greg said...

That would be great, but I don't know of a command-line tool to let me do XPath queries. Am I missing something?

I'm sure it wouldn't be hard to write a small tool using NSXMLDocument, but I was hoping for something standard and pre-installed.

Charles Albrecht said...

Curious. That warning doesn't appear in Tiger's version of the man page (which lists a Nov 3, 2003 mod date). I use this technique all the time for scripting Info.plist changes in an automated build process and it would be good to have a dedicated tool to do this.

Though, of course, remembering to make note of the change when Leopard (or, perhaps Leopard++) ships will take some remembering. Hopefully, there will be some overlap with the new tool.

Greg said...

I'm using Tiger and it's in the man page on my machine.


$ man 1 defaults | grep WARNING
WARNING: The defaults command will be changed in an upcoming

Anonymous said...

While not entirely sure, I believe it is very difficult (if not impossible) to modify nested values in XML plist files. There is a little known Apple tool called PlistBuddy that can accomplish that much more easily. It is inluded in a number of Apple Installer packages. For example one is located on my computer at:

/Library/Receipts/AdditionalEssentials.pkg/Contents/Resources/PlistBuddy

Anonymous said...

cont'd

Modifying XML plist files using the Defaults command that is.

Charles Albrecht said...

Bizarre. On my PPC Tiger machines (all upgraded along the way from Panther or earlier), the warning isn't there, but on my Intel Tiger machine it is.

I should check my freshly-imaged PPC Tiger Server box at work for another data point....

Anonymous said...

defaults read /Applications/TextEdit.app/Contents/Info NSServices | grep NSSendTypes

will let you read them. I'm not sure how you would write to them.

Greg said...

:-) That is the solution I'm currently using for the problem at hand. However, it's not a general solution because it relies on the fact that defaults happened to display the values for NSSendTypes on one line. So, yes, that works in this case.

However, it doesn't work if the number of items in the array (or whatever) are split onto multiple lines. For example, the following won't work as desired.

defaults read /Applications/iTunes.app/Contents/Info CFBundleDocumentTypes | grep CFBundleTypeMIMETypes

I actually don't want to write any values, I just mentioned it for completeness. But the defaults command will let you write complex types. Their example is

defaults write com.companyname.appname '{ "Default Color" = (255, 0, 0); "Default Font" = Helvetica; }';

Anonymous said...

Regarding advanced queries: AFAIK, the only tool in a standard MacOS X install that supports XPath queries is xmllint - but only in shell mode, unfortunately.

You could either script xmllint using expect, or install a more apropriate tool like xmlstarlet (available through both MacPorts and Fink).

Michael said...

I've found that you can indeed write nested values. For instance, you can add an item to the dock using this structure (remove spaces after < . I had to add a space since the server was thinking they were HTML tags):

defaults write com.apple.dock persistent-apps -array-add '< dict>< key>tile-data< /key>< dict>< key>file-data< /key>< dict>< key>_CFURLString< /key>< string>/Applications (Mac OS 9)/Internet Explorer 5.1.7/Internet Explorer< /string>< key>_CFURLStringType< /key>< integer>0< /integer>< /dict>< /dict>< /dict>'

The problem for me (my programming skills are rudimentary) is that this entry gets added at the end of the plist (and in this case, the dock). I've not found an easy way to change an existing nested value.

I imagine this would be possible by reading out the entire plist (e.g. defaults read com.apple.dock), making your changes, and then writing it all back with a syntax like the above.

Anonymous said...

Here's a bit more on PlistBuddy:
Modifying nested property list items with PlistBuddy
http://www.dreness.com/blog/archives/6

M M W said...

for the XPath aficionados,

http://plumber.gnu-darwin.org/home/pub/Documents/plist-to-dom.xsl.txt

Cheers!

Om said...
This comment has been removed by the author.
Om said...

Easiest way to edit the plist files is using plistBuddy. But i didn't find this on Leopard.
PlistBuddy will be available at /Library/Receipts\
/AdditionalEssentials.pkg\
/Contents/Resources/PlistBuddy on Tiger.

Defaults can write and delete a key in a plist file and most importantly defaults can't be used to edit values in a plist file.( I am not sure about this ). It can either delete a key or add a key But PlistBuddy can b e used to edit the plist file.

Example:
PlistBuddy -c "set CFBundleVersion 200" "/Applications/TextEdit.app/Contents/Info.plist"
It Sets the TextEdit's CFBundleVersion to 200

President Leechman said...

If you haven't installed Tiger from scratch, you'll need to look for the AdditionalEssentials package in "Mac OS X Install Disc 1" under "/System/Installation/Packages".