Originally Posted By: Virtual1
The end result then is, there's NO approved (or reliable for that matter) way to edit a plist's dictionaries and arrays as of right now. I have a complex script that constructs base images from scratch, and it makes extensive adjustments to plists. For things that require buddy, it kills the prefs daemon immediately after making changes, forcing it to recache them from disk, instead of overwriting them. Works most of the time. Editing settings in open apps (like Finder) is still unreliable.

I strongly recommend against killing cfprefsd. That discards all unsaved settings for all apps. Avoid the shotgun approach; it can get the job done, but can also lead to massive collateral damage. In fact, I get twitchy anytime I see someone casually suggesting killall. (All??? You want to kill all of them? Surely there's only one in particular you need to kill. Killing all of them seems like possible overkill. Sort of like Terminator going through the phone book killing all the Sarah Conners, when he only needed to kill one. And you see where it got him. The pattern was noticed before he got to her and she was forewarned. Overkill has its consequences.)


It sounds like you're pre-populating defaults for a new user. One option is to edit the .plist files while the user is not logged in. (cfprefsd is a UserAgent: one copy per user running only when the user is logged in.) Either become root to access ~otheruser/Library/Preferences, or use su -l otheruser to create a Terminal session as that user without firing up that user's UserAgents. Of course, in the latter case the first use of defaults will fire it up anyway, but it should read only the files corresponding to preference domains you ask about.

Alternatively, you can build the .plist file exactly as you want, but in a different directory, and then send the whole thing to cfprefsd using

defaults write app.bundle.id "$(defaults read path/to/the.plist)"

Ignore the part of the man defaults page that says the new value must be enclosed in single quotes. What they mean is that the entire value must be passed in as a single argument, which could be done with single quotes around a literal string, but is more conveniently done here with double-quotes around a calculated string.

Yes, it means you're piping the entire file into cfprefsd, but on the other hand you were expecting cfprefsd to re-read the entire file. Efficiency-wise it's a wash. And, you can still use PlistBuddy to your heart's content. (PlistBuddy now seems to be part of a standard install, but it's in /usr/libexec instead of one of the directories on the standard path. Much better than the old days when you had to paw through ~/Library/Receipts looking for a copy.)

You don't need to replace the entire file. For example, Finder's ListViewSettings is a complex value, but you could construct a .plist file containing just just that value, and then install it with

defaults write com.apple.Finder ListViewSettings "$(defaults read path/to/ListViewSettings.plist)"

That only works one level down, of course.

The new file doesn't need to be in any particular .plist format. It could be a text file in the old-fashioned NextStep property list format (which, conveniently, is the format that defaults read produces). In that case, you can edit it with any text-editing app, including vim, bbedit, sed, awk, et al., and then import it with

defaults write app.bundle.id "$(cat path/to/plist.txt)"

Which actually opens up more options for you. Your workflow can be

defaults read app.bundle.id [topLevelStructure] > path/to/temp.txt
edit the temp.txt file using any convenient tools
defaults write app.bundle.id [topLevelStructure] "$(cat path/to/temp.txt)"

In short, I think that playing nicely with cfprefsd may force you to change your ways, but the new ways needn't be noticeably more difficult for you.

Last edited by ganbustein; 12/01/14 06:48 PM. Reason: Fix misplaced double-quote