29

Consider a preference plist with a dict that contains an array:

Let's create it:

defaults write org.my.test '{aDict = {anArray = ();};}'

Then read it back to see the structure better:

$defaults read org.my.test
{
    aDict = {
        anArray = (
        );
    };
}

Now, how do I add a value to anArray using the defaults write command?

I know that there is the -array-add type for adding values to an array, but how do I specify the key path to the array element?

I've tried this, but that doesn't work:

defaults write org.my.test aDict.anArray -array-add "a value"

In fact, I need to add a non-string type, so I also need to be able to specify the type, e.g. -bool YES.

(Note: I cannot use PlistBuddy nor plutil as this needs to affect live preferences)

Thomas Tempelmann
  • 11,045
  • 8
  • 74
  • 149
  • Just bumped into this one myself. I wonder if you found any solution that also support cached preferences – Zohar81 Mar 04 '20 at 11:19

3 Answers3

9

Use plutil and your life will be better. Defaults doesn't support key paths.

> defaults write org.my.test '{aDict = {anArray = ();};}'

> defaults read org.my.test
{
    aDict =     {
        anArray =         (
        );
    };
}

> plutil -insert aDict.anArray.0 -bool YES ~/Library/Preferences/org.my.test.plist

> defaults read org.my.test
{
    aDict =     {
        anArray =         (
            1
        );
    };
}

I used defaults read just to prove that the expected inputs are the same, but you'll probably use plutil -p ~/Library/Preferences/org.my.test.plist to read the file instead if you start using plutil more.

Liyan Chang
  • 7,721
  • 3
  • 39
  • 59
  • 4
    The problem with this solution is that it won't work with preferences files, because those are cached since 10.8 or so, hence modifying the files on disk will not update the cache accordingly. And I need to do this with preferences ("User Defaults"). – Thomas Tempelmann Dec 20 '16 at 09:44
  • @ThomasTempelmann do you know where the cache is? Thinking about deleting the cache to invalidate it if I can find it, so that it'll have to check the original file – Pat Myron May 08 '18 at 06:12
  • 4
    @PatMyron It's cached in memory, by the daemon `cfprefsd`. You could force-quit it to flush its cache, but that's not a path I want to go down. – Thomas Tempelmann Mar 28 '19 at 11:20
0

There may be a way by exporting the entire preferences set to XML, then modify that text to include the changes, then re-importing the XML into the preferences.

Basically, you'd use defaults export to create the XML output, then something like sed (see here for an example), then use defaults import to get it back into the prefs.

I have not figured out a generic method for this, so this is just a stub, but if you figure it out, please edit this answer, as I'll make it a "community wiki" entry, so that anyone can modify it.

Thomas Tempelmann
  • 11,045
  • 8
  • 74
  • 149
0

This expands on Thomas Tempelmann's idea and Liyan Chang's work and deals with cfprefsd by using the defaults command to re-write the plist which appears to work with cfprefsd: 1 2

defaults write org.my.test '{aDict = {anArray = ();};}'
defaults export org.my.test /tmp/foo.plist
plutil -insert aDict.anArray.0 -bool YES /tmp/foo.plist
defaults import org.my.test /tmp/foo.plist

Or you can do all of it on one line:

defaults write org.my.test "$(defaults export org.my.test - | plutil -insert aDict.anArray.0 -bool YES - -o -)"

Here it is expanded out and easier to follow:

> defaults write org.my.test '{aDict = {anArray = ();};}'

> defaults export org.my.test -
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
    <key>aDict</key>
    <dict>
        <key>anArray</key>
        <array/>
    </dict>
</dict>
</plist>

> defaults export org.my.test /tmp/foo.plist

> plutil -insert aDict.anArray.0 -bool YES /tmp/foo.plist

> defaults import org.my.test /tmp/foo.plist

> defaults export org.my.test -                                   
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
    <key>aDict</key>
    <dict>
        <key>anArray</key>
        <array>
            <true/>
        </array>
    </dict>
</dict>
</plist>
> 

Also instead of doing defaults write org.my.test '{aDict = {anArray = ();};}' which makes it hard to know the types you can do it as a plist like defaults write org.my.test '<dict><key>aDict</key><dict><key>anArray</key><array/></dict></dict> which makes it easier to see the types but much longer.

Mark McKinstry
  • 2,536
  • 1
  • 15
  • 8