extensionUserDefaults{@objcdynamicpublicvarstars:Int{get{integer(forKey:#function)}set{set(newValue,forKey:#function)}}}guardletuserDefaults=UserDefaults(suiteName:"five.stars")else{exit(EXIT_FAILURE)}lettoken:NSKeyValueObservation=userDefaults.observe(\.stars,options:[.initial,.new]){defaults,_inletrating=defaults.starsletfullStars=String(repeating:"★",count:rating)letemptyStars=String(repeating:"☆",count:5-rating)letstars:String=fullStars+emptyStarsprint(stars)// 👆🏻 prints the initial value and whenever a new change is made.}RunLoop.current.run()// 👆🏻 stops the script from terminating

The main.swift content.

The script in action

UserDefault Location

Regardless of the platform we’re running on, UserDefaults are always stored in Property List files, which are XML files in disguise, where the elements alternate between key tags and other elements types.

This explains why we can store only just a few handful types in UserDefaults.

In iOS apps, the standard UserDefaults plist is stored in the app home directory under the Library/Preferences folder: the name of the file is the app bundle identifier, for example blog.fivestars.app.plist.

On macOS, all user applications UserDefaults are stored at ~/Library/Preferences, therefore this folder not only contains our app plist file, but all other apps as well.

This is one reason why our apps need to have an unique bundle identifiers.

This works great for apps, but our scripts don’t have any bundle identifier: where the UserDefaults data of our tools are stored? It turns out that scripts also use the same folder: instead of using a bundle identifier, the script standard UserDefaults plist is stored under the name of the script.

e.g. a Swift executable named hello will have its standard UserDefaults stored at ~/Library/Preferences/hello.plist.

What about UserDefaults suites? In this case the suite name will be the name of the plist file.

e.g. a script using UserDefaults(suiteName: "five.stars") will have this suite data stored at ~/Library/Preferences/five.stars.plist.

Scripts are unsandboxed processes, hence they can read/write any UserDefaults file located in ~/Library/Preferences/, all it takes is to know the plist name (a.k.a. the app bundle id):
want to read/edit the user preferences for…

Xcode? Use UserDefaults(suiteName: "com.apple.dt.Xcode")

Finder? Use UserDefaults(suiteName: "com.apple.finder").

Etc.

For an easy way to explore even more preferences of both system and 3rd party apps, I suggest to use the free Prefs Editor app.

Conclusions

In this article we’ve explored the behind the scenes of our scripts UserDefaults preferences, where they’re persisted in our machines, and how scripts can actually access to all apps UserDefaults.

Do your CLI tools store any preferences? What kind of preferences do you store? Do you use UserDefaults? Please let me know!