On macOS dscl
is a very useful to access data in the local user directory or another directory the Mac is bound to. For example you can read a user’s UID with:
$ dscl /Search read /Users/armin UniqueID
UniqueID: 501
This output looks easy enough to parse, you can just use cut
or awk
:
$ dscl /Search read /Users/armin UniqueID | cut -d ' ' -f 2
501
$ dscl /Search read /Users/armin UniqueID | awk '{print $2;}'
501
However, dscl
is a treacherous. Its output format changes, depending on the contents of an attribute. When an attribute value contains whitespace, the format of the output has two lines:
$ dscl /Search read /Users/armin RealName
RealName:
Armin Briegel
With attributes like the UID, it is fairly safe safe to assume that there will be no whitespace in the value. With other attributes, such as RealName
or NFSHomeDirectory
, you cannot make that prediction with certainty. Real names may or may not have been entered with a space. A user (or management script) may have changed their home directory to something starting with /Volumes/User HD/...
and your script may fail.
To remove this output ambiguity, dscl
has a -plist
option which will print the output as a property list:
$ dscl -plist . read /Users/armin RealName
<?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>dsAttrTypeStandard:RealName</key>
<array>
<string>Armin Briegel</string>
</array>
</dict>
</plist>
The resulting property list is a dict
containing a key with the native attribute name and an array
containing the values, even when there is only one value.
Having a property list is nice, but parsing property lists in a shell script is challenging. I have found two solutions
Xpath
You can use the xpath
tool extract data from the XML output:
$ dscl -plist . read /Users/armin RealName | xpath "//string[1]/text()" 2>/dev/null
Armin Briegel
Note that the xpath
output does not include a final new line character, which makes it look a bit strange.
The xpath
argument in detail means:
//string[1]
: the first of anystring
element/text()
the text contents of thatstring
object
This syntax makes a lot of assumptions about the property list input. I believe they are safe with the dscl
output. (Please test)
If you want to play around with xpath
syntax, I recommend using an interactive tool. I used this one from Code Beautify which worked well enough, but frankly I just randomly chose one from the list of search results for ‘xpath tester’. (If you can recommend a great one, let us know in the comments.)
PlistBuddy
As I said, the xpath
solution makes a lot of assumptions about the layout of the property list. A safer way of parsing property lists would be a dedicated tool, such as PlistBuddy
. However, PlistBuddy
does not read from stdin
. At least not voluntarily.
A few weeks ago Erik Berglund shared this trick on Mac Admins Slack which makes PlistBuddy
read the output from another command. We can adapt this for our use case:
$ /usr/libexec/PlistBuddy -c "print :dsAttrTypeStandard\:RealName:0" /dev/stdin <<< $(dscl -plist . read /Users/armin RealName)
Armin Briegel
Note that you have to escape the :
in the attribute name, since PlistBuddy
uses the colon as a path separator.
You can use this in scripts to assign the value to a variable with
realName=$(/usr/libexec/PlistBuddy -c "print :dsAttrTypeStandard\:RealName:0" /dev/stdin <<< $(dscl -plist . read /Users/$username RealName))
This uses nested command substitution with the $(... $(...) ...)
syntax which is not possible using backticks.
Either way, you can get a safe value from dscl
in shell script, whether it contains whitespace or not.
“Having a property list is nice, but parsing property lists in a shell script is challenging”
Yep. And this is when it’s probably nice to leave shell scripting and consider using Python instead, which is much better at working with structured, complex data.
I think the hackery is justifiable for single values. Anything more complex, and yes: Python or Swift to the rescue!
Hi, I suggest this xpath tester: https://extendsclass.com/xpath-tester.html