10

how can i parse the output of the OS X defaults read terminal command?

it appears to output the 'old' NeXTSTEP plist format; things that look like:

{
"Apple Global Domain" =     {
    AppleAntiAliasingThreshold = 4;
    AppleCollationOrder = root;

i tried writing the output to a file and converting with plutil, but it chokes:

> defaults read > defaults.txt
> plutil -convert xml1 defaults.txt
2014-02-02 21:29:14.856 plutil[56896:707] CFPropertyListCreateFromXMLData(): Old-style
plist parser: missing semicolon in dictionary on line 10835. Parsing will be abandoned.
Break on _CFPropertyListMissingSemicolon to debug.
defaults.txt: Property List error: Unexpected character { at line 1 / JSON error: No
value for key in object around character 28.

why, you ask?

i'd like to store the defaults values in git so i can keep a record as a change setting and diff after applying changes, but it seems the serialization in defaults read is not 'line order stable': dictionaries do not dump their keys in consistent order, causing a huge amount of noise. if i can parse defaults read, i can then pipe the data out through an order-consistent serializer.

nrser
  • 1,287
  • 1
  • 13
  • 23
  • What are you reading? – trojanfoe Feb 02 '14 at 14:10
  • 1
    @trojanfoe the output of the `defaults read` terminal command (i'm using OS X 10.8 and 10.9). it dumps your preferences, but in what looks like an old NeXTSTEP plist format, as opposed to something like xml that would be easy to parse. i'll edit the question to try and make that clearer. – nrser Feb 02 '14 at 14:21
  • I need the same functionality for parsing the output from "/System/Library/CoreServices/pbs -dump_pboard" – Thomas Tempelmann Sep 18 '15 at 16:17
  • 1
    In fact, the format is quite close to JSON, only using () instead of [] for arrays, and ending every item with a ";" even if it's the last item in its level. So, perhaps one could use an existing JSON parser source and modify it slightly to get it to read this. – Thomas Tempelmann Sep 18 '15 at 16:21

6 Answers6

4

plutil can convert to xml or json

#!/bin/bash
f='~/Library/Preferences/com.apple.LaunchServices/com.apple.launchservices.secure'
x="$(plutil -convert xml1 -o - "$f")"
j="$(plutil -convert json -o - "$f")"

-o - means: print result to stdout
$f is the input file in plist format

milahu
  • 2,447
  • 1
  • 18
  • 25
3

You’re a lucky guy, just few days ago someone released a parser of the NeXTSTEP plist format on PyPi – nsplist.

Jakub Jirutka
  • 10,269
  • 4
  • 42
  • 35
  • 1
    thanks for the suggestion. i tried the `nsplist` library, but it pukes on my output from `defaults read`... my guess at this point is that the command is formatting it's response in a way that looks like a NeXTSTEP plist but isn't completely conformant (hence Apple's own `plutil` failing as well). – nrser Feb 13 '14 at 16:42
  • Not so lucky, because the output of 'defaults read' is NOT the old NextStep serialized string format, and actually - not anything meant to be parsed. Would be lucky to read my answer... 'defaults export' instead of 'defaults read' gives you a standard plist or XML representation that is perfectly parsable/usable. – Motti Shneor Dec 21 '22 at 08:10
2

Instead of parsing the output of the defaults command, here's a script (uses Swift) that reads the defaults directly:

#!/usr/bin/swift

// Usage:
// dread com.apple.dt.Xcode IDEApplicationwideBuildSettings   
// dread com.apple.dt.Xcode IDEApplicationwideBuildSettings.SYMROOT
// Example:
//         $ dread com.apple.Terminal "Default Window Settings"
// returns $ Basic

import Foundation

var args = CommandLine.arguments
args.removeFirst()
if let suite = args.first, let defaults = UserDefaults(suiteName: suite) {
    args.removeFirst()
    if let keyPath = args.first, let value = defaults.value(forKeyPath: keyPath) {
        print(value)
    } else {
        print(defaults.dictionaryRepresentation())
    }
}

If you save this as a file called dread, chmod +x dread, you can then read an application's defaults and print them out like so:

dread com.apple.dt.Xcode IDEApplicationwideBuildSettings

Since this is a dictionary, you can read one of the keys from that dictionary using:

dread com.apple.dt.Xcode IDEApplicationwideBuildSettings.SYMROOT

And so on.

PotatoFarmer
  • 2,755
  • 2
  • 16
  • 26
Patrick Beard
  • 592
  • 6
  • 11
2

All of these comments are trying to be helpful, but seem to be from people who have not actually tried to deal with defaults read output on recent (at least post-2014) macOS.

The output is not valid JSON or old-style, almost-JSON NS. It is close. It cannot be parsed by any of the suggested answers.

This is frustrating and not at all unusual in the world of macOS development.

1

Excuse me answering a close variation of your question.

You see, the defaults command-line tool emits by default a string representation of the content hierarchy, known as [NSObject description], a format that isn't formal, isn't completely documented, and one that can't be relied on. You can see this format if you emit an NSObject descendent in any log line:

NSLog(@"This is the string representation of a dictionary: %@", myDictionary); 

or in the Xcode debugger (LLDB):

po myDictionary

and of course - when you use the 'defaults read' command from shell. This format is not meant to be parsed, and may lose information, and is NOT considered any proper "serialization" of the printed object.

However - exactly because of this, the defaults command provides the export sub-command, as an alternative to read.

Frustratingly enough, this sub-command is NOT documented in man defaults and only by chance I found it in defaults help or defaults -h. Here is the relevant excerpt:

  export <domain> <path to plist>      saves domain as a binary plist to path
  export <domain> -                    writes domain as an xml plist to stdout

The output of these, whether on disk file or as xml printed to stdout, can be safely and easily parsed using the normal Cocoa machinery (Foundation collection initializers from file or data, the NSPropertyListSerialization protocol etc. For example, if you collect the stdout of the second variation into an NSData object, then parsing it will look like this:

NSData *outputOfDefaultsCommand;
NSError *error;
NSPropertyListFormat format;
NSDictionary *dict = [NSPropertyListSerialization propertyListWithData: outputOfDefaultsCommand options:NSPropertyListImmutable format:&format error:&error];
if ( !dict || error)
    NSLog(@"Failed to read NSDictionary from .plist data output of 'defaults' command. Error:%@", error);
else 
    NSLog(@"Parsed 'defaults' output: %@", dict);
Motti Shneor
  • 2,095
  • 1
  • 18
  • 24
0

Have you tried defaults export? This outputs a binary file that then can be converted to a .plist using plutil

David Dean
  • 2,682
  • 23
  • 34