swift: Calling "/usr/bin/defaults" returns no data

I'd like to create a small helper app for new students do read/write User default settings.

Since it was not possible using the UserDefaults class I decided to use the "/usr/bin/defaults". Unfortuntely it seems not to return anything. Debug output shows "Got data: 0 bytes"

Here is a sample code:

import SwiftUI

func readDefaults(domain : String, key :String) -> String {
    let cmdPath = "/usr/bin/defaults"
    //let cmdPath = "/bin/ls"

    let cmd  = Process()
    let pipe = Pipe()
    cmd.standardOutput = pipe
    cmd.standardError  = pipe
    
    cmd.executableURL = URL(fileURLWithPath: cmdPath, isDirectory: false, relativeTo: nil)
    cmd.arguments = ["read", domain, key]
    //cmd.arguments = ["/", "/Library"]

    print("Shell command: \(cmdPath) \(cmd.arguments?.joined(separator: " ") ?? "")")
    var d : Data?
    do {
        try cmd.run()
        d = pipe.fileHandleForReading.readDataToEndOfFile()
        cmd.waitUntilExit()
    } catch let e as NSError {
        return "ERROR \(e.code): \(e.localizedDescription)"
    } catch {
        return "ERROR: call failed!"
    }
    
    // get pipe output and write is to stdout
    guard let d else {
        return "ERROR: Can't get pipe output from command!"
    }
    print("Got data: \(d)")
    if let s = String(data: d, encoding: String.Encoding.utf8) {
        print("Got result: \(s)")
        return s
    } else {
        return "ERROR: No output from pipe."
    }
}


struct ContentView: View {
    let foo = readDefaults(domain: "com.apple.Finder", key: "ShowHardDrivesOnDesktop")
    
    var body: some View {
        VStack {
            Text("ShowHardDrivesOnDesktop: \(foo.description)")
        }
        .padding()
    }
}

#Preview {
    ContentView()
}

This code works well e.g. for "ls" when the comments are changed for cmdPath and cmd.arguments.

What do I miss in order to get it working with defaults?

Answered by DTS Engineer in 881497022
Since it was not possible using the UserDefaults class

What’s not possible? Reading a user default from a specific domain, like com.apple.Finder?

You are correct that UserDefaults can’t do that, but running the defaults tool is not the best alternative. Rather, use CFPreferences. For example:

import Foundation

func main() {
    guard
        let obj = CFPreferencesCopyAppValue("ShowHardDrivesOnDesktop" as NSString, "com.apple.Finder" as NSString),
        CFGetTypeID(obj) == CFBooleanGetTypeID(),
        let showHardDrivesOnDesktop = obj as? Bool
    else {
        fatalError()
    }
    print(showHardDrivesOnDesktop)
}

main()

WARNING Unless otherwise documented, system preferences like this are an implementation detail. It might be OK to use them in a limited scope — like in a managed environment where you control all the copies of the code — but you should not rely on implementation details in a product that you deploy widely.

The code above won’t work if your app is sandboxed. Then again, defaults won’t work in that case either (-

Finally, while you shouldn’t need to use defaults in this case, if you find yourself in a situation where the only way to do something is by invoking a command-line tool, I strongly recommend that you use the Subprocess package rather than Process. Process is really hard to use correctly.

Share and Enjoy

Quinn “The Eskimo!” @ Developer Technical Support @ Apple
let myEmail = "eskimo" + "1" + "@" + "apple.com"

Strange: In my test application I removed "Apple Sandbox" and now it works here. Using the same settings it does not work in my real app.

I guess there is a wrong Xcode setting for my target. Need to investigate…

Thanks for your post. I have a question about your post.

Since it was not possible using the UserDefaults class

Why is not possible to use the UserDefaults?

https://developer.apple.com/documentation/foundation/userdefaults

Thanks

Albert Pascual
  Worldwide Developer Relations.

Accepted Answer
Since it was not possible using the UserDefaults class

What’s not possible? Reading a user default from a specific domain, like com.apple.Finder?

You are correct that UserDefaults can’t do that, but running the defaults tool is not the best alternative. Rather, use CFPreferences. For example:

import Foundation

func main() {
    guard
        let obj = CFPreferencesCopyAppValue("ShowHardDrivesOnDesktop" as NSString, "com.apple.Finder" as NSString),
        CFGetTypeID(obj) == CFBooleanGetTypeID(),
        let showHardDrivesOnDesktop = obj as? Bool
    else {
        fatalError()
    }
    print(showHardDrivesOnDesktop)
}

main()

WARNING Unless otherwise documented, system preferences like this are an implementation detail. It might be OK to use them in a limited scope — like in a managed environment where you control all the copies of the code — but you should not rely on implementation details in a product that you deploy widely.

The code above won’t work if your app is sandboxed. Then again, defaults won’t work in that case either (-

Finally, while you shouldn’t need to use defaults in this case, if you find yourself in a situation where the only way to do something is by invoking a command-line tool, I strongly recommend that you use the Subprocess package rather than Process. Process is really hard to use correctly.

Share and Enjoy

Quinn “The Eskimo!” @ Developer Technical Support @ Apple
let myEmail = "eskimo" + "1" + "@" + "apple.com"

Many thanks Quinn.

I prefer plain Swift Classes and therefore I did not look at the Preferences Utilities. I will look into this and I agree that this is a much better solution than using the shell command.

For the records: I use now the Prefrerences Utilities. The readBool code was already given by Quinn above. Here is my writeBool code:

    func writeBool(domain: String, key: String, value: Bool) {
        let appID = domain as CFString
        let cfKey = key as CFString
        let cfValue: CFPropertyList = value as CFBoolean
        CFPreferencesSetValue(cfKey, cfValue, appID, kCFPreferencesCurrentUser, kCFPreferencesAnyHost)
        CFPreferencesSynchronize(appID, kCFPreferencesCurrentUser, kCFPreferencesAnyHost)
    }
swift: Calling "/usr/bin/defaults" returns no data
 
 
Q