{"id":1570,"date":"2025-03-11T10:17:33","date_gmt":"2025-03-11T15:17:33","guid":{"rendered":"https:\/\/www.brunerd.com\/blog\/?p=1570"},"modified":"2025-04-22T23:54:32","modified_gmt":"2025-04-23T04:54:32","slug":"introducing-two-plist-tools-plb-and-plx","status":"publish","type":"post","link":"https:\/\/www.brunerd.com\/blog\/2025\/03\/11\/introducing-two-plist-tools-plb-and-plx\/","title":{"rendered":"introducing two plist tools plb and plx"},"content":{"rendered":"\n<h2 class=\"wp-block-heading\">Elevator pitch<\/h2>\n\n\n\n<p>I&#8217;m excited to introduce two plist tools for macOS <code><a href=\"https:\/\/github.com\/brunerd\/plb\" target=\"_blank\" rel=\"noreferrer noopener\">plb<\/a><\/code> the <strong>plist broker<\/strong> and <code><a href=\"https:\/\/github.com\/brunerd\/plx\" target=\"_blank\" rel=\"noreferrer noopener\">plx<\/a><\/code> the <strong>plist extractor<\/strong>. The <em>value<\/em> of <code><code><a href=\"https:\/\/github.com\/brunerd\/plb\" target=\"_blank\" rel=\"noreferrer noopener\">plb<\/a><\/code><\/code> is its ability to <strong>visualize<\/strong> a plist as you will see below, just like my tool <code><a href=\"https:\/\/github.com\/brunerd\/jpt\">jpt<\/a><\/code> does for JSON. <code><code><a href=\"https:\/\/github.com\/brunerd\/plb\" target=\"_blank\" rel=\"noreferrer noopener\">plb<\/a><\/code><\/code> can map out the complete paths and values that may be buried within nested dictionaries and arrays. It can help you see the &#8220;shape&#8221; of a plist and recognize patterns. They are both shell scripts <em>and<\/em> functions you can incorporate into <em>your<\/em> own scripts, like my other embeddable tools: <code><a href=\"https:\/\/github.com\/brunerd\/jpt\" target=\"_blank\" rel=\"noreferrer noopener\">jpt<\/a><\/code>, <code><a href=\"https:\/\/github.com\/brunerd\/ljt\" target=\"_blank\" rel=\"noreferrer noopener\">ljt<\/a><\/code>, <code><a href=\"https:\/\/github.com\/brunerd\/jse\" target=\"_blank\" rel=\"noreferrer noopener\">jse<\/a><\/code> and <code><a href=\"https:\/\/github.com\/brunerd\/shui\" target=\"_blank\" rel=\"noreferrer noopener\">shui<\/a><\/code><\/p>\n\n\n\n<h2 class=\"wp-block-heading\">Because Reasons, part 1<\/h2>\n\n\n\n<p>It was while writing <a href=\"https:\/\/www.brunerd.com\/blog\/2024\/12\/04\/detecting-apple-intelligence-and-chatgpt-integration-status\/\">Detecting Apple Intelligence and ChatGPT Integration Status<\/a> last November, I decided to make a plist tool that could thoroughly map and navigate plist paths, easily extract plain text values and most importantly, effortlessly navigate into plists stored as base64 encoded data. What I thought was a new trend, is actually <em>not<\/em>: Binary plist data in plists is <em>nothing new<\/em> and if <em>that&#8217;s<\/em> the case then, it&#8217;s about time we have a tool for work with them! Funnily enough, in the above post I complained with an &#8220;ATTN: Craig Federighi&#8221; about the obfuscation games I felt Apple engineers were playing with the Apple Intelligence prefs as these inscrutable data blobs. Now, in the upcoming macOS 15.4 release, lo and behold those keys have <em>disappeared<\/em> from <code>.GlobalPreferences<\/code>! \ud83d\udca5 Um, <em>thanks<\/em>, I guess? &#8220;I see dead keys&#8221; now. \ud83d\ude2c &#8220;Be careful what you wish for, it might diminish your tool&#8217;s relevance!&#8221; as they (<em>kinda<\/em>) say. Still, there&#8217;s <em>lots<\/em> of binary plist data embedded in your preferences, when you run the examples for <code><code><a href=\"https:\/\/github.com\/brunerd\/plb\" target=\"_blank\" rel=\"noreferrer noopener\">plb<\/a><\/code><\/code> below you&#8217;ll see.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">Because Reasons, part 2<\/h2>\n\n\n\n<p>You might <em>still<\/em> be asking yourself: &#8220;<em>Why<\/em> do we need yet another plist tool though? We have <code>defaults<\/code>, <code>PlistBuddy<\/code>, and <code>plutil<\/code>. Why do we need anything else?!&#8221;. Well, let me tell you they all have their strengths and weaknesses:<\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li><code>defaults<\/code> can only get the value of a top level key, it does not work with nested paths; it cannot output XML unless it is the entire plist; it converts ISO 8601 dates to local time using abbreviated time zone which are notoriously hard to parse with <code>date<\/code>; empty preferences return an error as if they don&#8217;t exist; it cannot take piped input; binary data is output as an abridged hex dump<\/li>\n\n\n\n<li>With <code>plutil -extract<\/code> there is no way to extract data from the root of a non-dictionary type plist, a key name is <em>always<\/em> required; key paths are period delimited, so keys with periods in their name require <code>\\\\<\/code> escapes which gets unsightly quickly with when reverse domain style key names are used;  the JSON extract method balks at outputting most plist data types (real, boolean, date, data); the <code>-convert xml1<\/code> function makes it too easy to overwrite the source file if you forget to add the awkward <code>-o -<\/code> syntax to send to stdout versus the source file<\/li>\n\n\n\n<li><code>PlistBuddy<\/code> is a mixed bag: it can work with paths; it has a colon delimited key path notation that&#8217;s not flummoxed by periods; colons are more easily escaped with a single vs. double slash; it can output XML with <code>-x<\/code>; however, it cannot process binary plist data; it adds a newline to all output which alters binary output and especially <em>corrupt<\/em>s binary plist data; redirected input requires <code>\/dev\/stdin<\/code> to be specified as the file; piped input does <em>not<\/em> work, only here documents do (awkward looking one-liners ensue)<\/li>\n<\/ul>\n\n\n\n<h2 class=\"wp-block-heading\">plb in action<\/h2>\n\n\n\n<h3 class=\"wp-block-heading\">Getting the lay of the land<\/h3>\n\n\n\n<p>One of the most powerful features came about quite late in development: &#8220;crawling&#8221;. That is, <code><code><a href=\"https:\/\/github.com\/brunerd\/plb\" target=\"_blank\" rel=\"noreferrer noopener\">plb<\/a><\/code><\/code> can iterate over a plist outputting paths, types and values (depending on the specified depth) and even traverse into plist data stored as data. The go-to exploratory command is: <code>plb -V<\/code> (which is shorthand for <code>-CCC<\/code>,  the 3rd level of crawl output):<\/p>\n\n\n<div class=\"wp-block-image\">\n<figure class=\"aligncenter size-full is-resized\"><a href=\"https:\/\/www.brunerd.com\/blog\/wp-content\/uploads\/image-92.png\"><img loading=\"lazy\" decoding=\"async\" width=\"852\" height=\"934\" src=\"https:\/\/www.brunerd.com\/blog\/wp-content\/uploads\/image-92.png\" alt=\"\" class=\"wp-image-1652\" style=\"width:469px;height:auto\" srcset=\"https:\/\/www.brunerd.com\/blog\/wp-content\/uploads\/image-92.png 852w, https:\/\/www.brunerd.com\/blog\/wp-content\/uploads\/image-92-274x300.png 274w, https:\/\/www.brunerd.com\/blog\/wp-content\/uploads\/image-92-768x842.png 768w\" sizes=\"auto, (max-width: 852px) 100vw, 852px\" \/><\/a><figcaption class=\"wp-element-caption\">Path &gt;&gt;&gt; type &gt;&gt;&gt; value<\/figcaption><\/figure>\n<\/div>\n\n\n<p>Every simple type, except <code>data<\/code>, can be seen above: <code>string<\/code>, <code>bool<\/code>, <code>integer<\/code>, <code>real<\/code> and <code>date<\/code>. As well as the &#8220;complex&#8221; types of <code>array<\/code> and <code>dict<\/code>. An array&#8217;s indices are enumerated and a dictionary will have all paths traced with colons delimiting the path of additional dictionaries and arrays within. Let&#8217;s see what happens when we hit some data type nodes:<\/p>\n\n\n<div class=\"wp-block-image\">\n<figure class=\"aligncenter size-large is-resized\"><a href=\"https:\/\/www.brunerd.com\/blog\/wp-content\/uploads\/image-93.png\"><img loading=\"lazy\" decoding=\"async\" width=\"1024\" height=\"440\" src=\"https:\/\/www.brunerd.com\/blog\/wp-content\/uploads\/image-93-1024x440.png\" alt=\"\" class=\"wp-image-1653\" style=\"width:599px;height:auto\" srcset=\"https:\/\/www.brunerd.com\/blog\/wp-content\/uploads\/image-93-1024x440.png 1024w, https:\/\/www.brunerd.com\/blog\/wp-content\/uploads\/image-93-300x129.png 300w, https:\/\/www.brunerd.com\/blog\/wp-content\/uploads\/image-93-768x330.png 768w, https:\/\/www.brunerd.com\/blog\/wp-content\/uploads\/image-93.png 1388w\" sizes=\"auto, (max-width: 1024px) 100vw, 1024px\" \/><\/a><figcaption class=\"wp-element-caption\">Each indented section is a decoded binary blob of plist data<\/figcaption><\/figure>\n<\/div>\n\n\n<p>As you can see <code>the<\/code> <code>-V<\/code> will cause <code><code><a href=\"https:\/\/github.com\/brunerd\/plb\" target=\"_blank\" rel=\"noreferrer noopener\">plb<\/a><\/code><\/code> to go <em>into<\/em> property list data within data types, a lowercase <code>-v<\/code> will <em>not<\/em> traverse into plists encoded as data (same with upper\/lowercase crawl option <code>-c<\/code>\/<code>-cc<\/code> and <code>-C<\/code>\/<code>-CC<\/code>). When this &#8220;traversal&#8221; occurs, the path is indented for easier visual identification and the pipe character <code>|<\/code> is used to delimit. In the above pic we can see both empty and populated arrays. There are some preferences like <code>com.apple.ncplugin.weather<\/code> that have two levels of nesting!<\/p>\n\n\n<div class=\"wp-block-image\">\n<figure class=\"aligncenter size-full is-resized\"><a href=\"https:\/\/www.brunerd.com\/blog\/wp-content\/uploads\/image-94.png\"><img loading=\"lazy\" decoding=\"async\" width=\"756\" height=\"424\" src=\"https:\/\/www.brunerd.com\/blog\/wp-content\/uploads\/image-94.png\" alt=\"\" class=\"wp-image-1654\" style=\"width:435px;height:auto\" srcset=\"https:\/\/www.brunerd.com\/blog\/wp-content\/uploads\/image-94.png 756w, https:\/\/www.brunerd.com\/blog\/wp-content\/uploads\/image-94-300x168.png 300w\" sizes=\"auto, (max-width: 756px) 100vw, 756px\" \/><\/a><figcaption class=\"wp-element-caption\">Is this the &#8220;Inception&#8221; of plists?\ud83e\ude86<\/figcaption><\/figure>\n<\/div>\n\n\n<p>Lastly, here&#8217;s what binary data looks like in <code>-v<\/code>\/<code>-V<\/code> mode. The <code>file<\/code> type is inside parentheses after <code>data<\/code> and on the next line(s) are the binary data rendered with <code>xxd<\/code><\/p>\n\n\n<div class=\"wp-block-image\">\n<figure class=\"aligncenter size-large is-resized\"><a href=\"https:\/\/www.brunerd.com\/blog\/wp-content\/uploads\/image-100.png\"><img loading=\"lazy\" decoding=\"async\" width=\"1024\" height=\"507\" src=\"https:\/\/www.brunerd.com\/blog\/wp-content\/uploads\/image-100-1024x507.png\" alt=\"\" class=\"wp-image-1668\" style=\"width:641px;height:auto\" srcset=\"https:\/\/www.brunerd.com\/blog\/wp-content\/uploads\/image-100-1024x507.png 1024w, https:\/\/www.brunerd.com\/blog\/wp-content\/uploads\/image-100-300x148.png 300w, https:\/\/www.brunerd.com\/blog\/wp-content\/uploads\/image-100-768x380.png 768w, https:\/\/www.brunerd.com\/blog\/wp-content\/uploads\/image-100-1536x760.png 1536w, https:\/\/www.brunerd.com\/blog\/wp-content\/uploads\/image-100.png 1540w\" sizes=\"auto, (max-width: 1024px) 100vw, 1024px\" \/><\/a><\/figure>\n<\/div>\n\n\n<h3 class=\"wp-block-heading\">More work with embedded plist data<\/h3>\n\n\n\n<p>Now that we&#8217;ve checked out the &#8220;crawling&#8221; output,  let&#8217;s take a look at the key that inspired all this work <code>com.apple.gms.availability.key<\/code>. You probably won&#8217;t find this key on <em>new<\/em> 15.4 installs but it might hang around if you upgraded from 15.0 to 15.3. In this example, if you ran <code>defaults export .GlobalPreferences -<\/code> you&#8217;d only see the key as a base64 encoded data string and you could get just that key in XML. If you specify the key <code>defaults<\/code> gives you an <em>abbreviated<\/em> (and useless) hex dump. <\/p>\n\n\n<div class=\"wp-block-image\">\n<figure class=\"aligncenter size-full is-resized\"><a href=\"https:\/\/www.brunerd.com\/blog\/wp-content\/uploads\/image-95.png\"><img loading=\"lazy\" decoding=\"async\" width=\"1018\" height=\"232\" src=\"https:\/\/www.brunerd.com\/blog\/wp-content\/uploads\/image-95.png\" alt=\"\" class=\"wp-image-1655\" style=\"width:517px;height:auto\" srcset=\"https:\/\/www.brunerd.com\/blog\/wp-content\/uploads\/image-95.png 1018w, https:\/\/www.brunerd.com\/blog\/wp-content\/uploads\/image-95-300x68.png 300w, https:\/\/www.brunerd.com\/blog\/wp-content\/uploads\/image-95-768x175.png 768w\" sizes=\"auto, (max-width: 1018px) 100vw, 1018px\" \/><\/a><figcaption class=\"wp-element-caption\">I don&#8217;t speak robot, do you? \ud83e\udd16<\/figcaption><\/figure>\n<\/div>\n\n\n<p>If you use <code>plutil<\/code> to extract the value of the key, it&#8217;d not only require some tedious escaping of the periods but would still only get you the base64 encoded string which would require piping into another <code>plutil<\/code> command to convert to <code>xml1<\/code>. Not to mention the <code>-extract<\/code> syntax always throws me off!<\/p>\n\n\n<div class=\"wp-block-image\">\n<figure class=\"aligncenter size-large is-resized\"><a href=\"https:\/\/www.brunerd.com\/blog\/wp-content\/uploads\/image-96.png\"><img loading=\"lazy\" decoding=\"async\" width=\"1024\" height=\"212\" src=\"https:\/\/www.brunerd.com\/blog\/wp-content\/uploads\/image-96-1024x212.png\" alt=\"\" class=\"wp-image-1656\" style=\"width:654px;height:auto\" srcset=\"https:\/\/www.brunerd.com\/blog\/wp-content\/uploads\/image-96-1024x212.png 1024w, https:\/\/www.brunerd.com\/blog\/wp-content\/uploads\/image-96-300x62.png 300w, https:\/\/www.brunerd.com\/blog\/wp-content\/uploads\/image-96-768x159.png 768w, https:\/\/www.brunerd.com\/blog\/wp-content\/uploads\/image-96.png 1374w\" sizes=\"auto, (max-width: 1024px) 100vw, 1024px\" \/><\/a><figcaption class=\"wp-element-caption\">Two more ways to get nothing particularly useful<\/figcaption><\/figure>\n<\/div>\n\n<div class=\"wp-block-image\">\n<figure class=\"aligncenter size-large is-resized\"><a href=\"https:\/\/www.brunerd.com\/blog\/wp-content\/uploads\/image-99.png\"><img loading=\"lazy\" decoding=\"async\" width=\"1024\" height=\"133\" src=\"https:\/\/www.brunerd.com\/blog\/wp-content\/uploads\/image-99-1024x133.png\" alt=\"\" class=\"wp-image-1663\" style=\"width:654px;height:auto\" srcset=\"https:\/\/www.brunerd.com\/blog\/wp-content\/uploads\/image-99-1024x133.png 1024w, https:\/\/www.brunerd.com\/blog\/wp-content\/uploads\/image-99-300x39.png 300w, https:\/\/www.brunerd.com\/blog\/wp-content\/uploads\/image-99-768x99.png 768w, https:\/\/www.brunerd.com\/blog\/wp-content\/uploads\/image-99.png 1360w\" sizes=\"auto, (max-width: 1024px) 100vw, 1024px\" \/><\/a><figcaption class=\"wp-element-caption\">Does the &#8220;juice look worth the squeeze&#8221; in either of these options?<\/figcaption><\/figure>\n<\/div>\n\n\n<p><code>PlistBuddy<\/code> can <em>kinda<\/em> output the raw binary plist <em>except<\/em> it appends a newline to <em>everything<\/em>, which corrupts the binary plist data and <code>plutil<\/code> fails. <code>perl<\/code> can chomp it off or in <code>zsh<\/code> you can capture the output using command substitution <code>$()<\/code> since zsh can handle nulls in variables this will strip the extraneous newline <em>but<\/em> again, you&#8217;d <em>still<\/em> have to convert or extract the value <code>plutil<\/code>!  <em>Instead<\/em>, let&#8217;s check out how <strong>easy<\/strong> it is with <code><code><a href=\"https:\/\/github.com\/brunerd\/plb\" target=\"_blank\" rel=\"noreferrer noopener\">plb<\/a><\/code><\/code>. Let&#8217;s again start with the encoded XML output: <code>plb -x .GlobalPreferences<\/code> <code>com.apple.gms.availability.key<\/code> <\/p>\n\n\n<div class=\"wp-block-image\">\n<figure class=\"aligncenter size-full is-resized\"><a href=\"https:\/\/www.brunerd.com\/blog\/wp-content\/uploads\/image-84.png\"><img loading=\"lazy\" decoding=\"async\" width=\"978\" height=\"258\" src=\"https:\/\/www.brunerd.com\/blog\/wp-content\/uploads\/image-84.png\" alt=\"\" class=\"wp-image-1585\" style=\"width:493px;height:auto\" srcset=\"https:\/\/www.brunerd.com\/blog\/wp-content\/uploads\/image-84.png 978w, https:\/\/www.brunerd.com\/blog\/wp-content\/uploads\/image-84-300x79.png 300w, https:\/\/www.brunerd.com\/blog\/wp-content\/uploads\/image-84-768x203.png 768w\" sizes=\"auto, (max-width: 978px) 100vw, 978px\" \/><\/a><figcaption class=\"wp-element-caption\">This again but much easier to get to.<\/figcaption><\/figure>\n<\/div>\n\n\n<p><code>-x<\/code> ensures XML output <em>and<\/em> it also keeps the data from being automatically decoded and output as plaintext. How about we &#8220;traverse&#8221; into the embedded plist data and output it as XML. We simply append a pipe character <code>|<\/code> to the end of the path, like we saw in the &#8220;crawled&#8221; paths above. Make sure to either escape or quote the pipe character in the path argument like this (so the shell does not misunderstand): <code>plb -x .GlobalPreferences 'com.apple.gms.availability.key<\/code>|:&#8217; (the root colon is optional, fyi)<\/p>\n\n\n<div class=\"wp-block-image\">\n<figure class=\"aligncenter size-large is-resized\"><a href=\"https:\/\/www.brunerd.com\/blog\/wp-content\/uploads\/image-97.png\"><img loading=\"lazy\" decoding=\"async\" width=\"1024\" height=\"189\" src=\"https:\/\/www.brunerd.com\/blog\/wp-content\/uploads\/image-97-1024x189.png\" alt=\"\" class=\"wp-image-1657\" style=\"width:667px;height:auto\" srcset=\"https:\/\/www.brunerd.com\/blog\/wp-content\/uploads\/image-97-1024x189.png 1024w, https:\/\/www.brunerd.com\/blog\/wp-content\/uploads\/image-97-300x55.png 300w, https:\/\/www.brunerd.com\/blog\/wp-content\/uploads\/image-97-768x142.png 768w, https:\/\/www.brunerd.com\/blog\/wp-content\/uploads\/image-97.png 1226w\" sizes=\"auto, (max-width: 1024px) 100vw, 1024px\" \/><\/a><\/figure>\n<\/div>\n\n\n<p>Wow, this is a lot easier already! You can also crawl the key itself with <code>-V<\/code> and get simple overview<\/p>\n\n\n<div class=\"wp-block-image\">\n<figure class=\"aligncenter size-full is-resized\"><a href=\"https:\/\/www.brunerd.com\/blog\/wp-content\/uploads\/image-102.png\"><img loading=\"lazy\" decoding=\"async\" width=\"744\" height=\"118\" src=\"https:\/\/www.brunerd.com\/blog\/wp-content\/uploads\/image-102.png\" alt=\"\" class=\"wp-image-1673\" style=\"width:422px;height:auto\" srcset=\"https:\/\/www.brunerd.com\/blog\/wp-content\/uploads\/image-102.png 744w, https:\/\/www.brunerd.com\/blog\/wp-content\/uploads\/image-102-300x48.png 300w\" sizes=\"auto, (max-width: 744px) 100vw, 744px\" \/><\/a><\/figure>\n<\/div>\n\n\n<p>Since the array has only a simple integer value, it will easily output as plain text with this command: <code>plb .GlobalPreferences com.apple.gms.availability.key<\/code><\/p>\n\n\n<div class=\"wp-block-image\">\n<figure class=\"aligncenter size-full is-resized\"><a href=\"https:\/\/www.brunerd.com\/blog\/wp-content\/uploads\/image-82.png\"><img loading=\"lazy\" decoding=\"async\" width=\"682\" height=\"60\" src=\"https:\/\/www.brunerd.com\/blog\/wp-content\/uploads\/image-82.png\" alt=\"\" class=\"wp-image-1582\" style=\"width:429px;height:auto\" srcset=\"https:\/\/www.brunerd.com\/blog\/wp-content\/uploads\/image-82.png 682w, https:\/\/www.brunerd.com\/blog\/wp-content\/uploads\/image-82-300x26.png 300w\" sizes=\"auto, (max-width: 682px) 100vw, 682px\" \/><\/a><figcaption class=\"wp-element-caption\"><code><code><a href=\"https:\/\/github.com\/brunerd\/plb\" target=\"_blank\" rel=\"noreferrer noopener\">plb<\/a><\/code><\/code>: Keeping it simple (so-and-so)<\/figcaption><\/figure>\n<\/div>\n\n\n<p>Look at <em>that<\/em>, would you! A simple number from a <em>simple<\/em> command, neato! BTW: While this key might be going the way of the dodo, the value in macOS 15.0-15.3 indicates (in a roundabout way) that Apple Intelligence is <strong>off<\/strong> (<code>2<\/code>) or <strong>on<\/strong> (<code>0<\/code>) \u2013 <code>1<\/code> I think means it&#8217;s downloading and installing. I built <code><code><a href=\"https:\/\/github.com\/brunerd\/plb\" target=\"_blank\" rel=\"noreferrer noopener\">plb<\/a><\/code><\/code> to attempt to give you plaintext output by default. Only a <code>dict<\/code> or <code>data<\/code> key will cause output to be XML, otherwise all &#8220;simple&#8221; types are be output as plain text.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">What&#8217;s plx for then?<\/h2>\n\n\n\n<p>Glad you asked! Despite making <a href=\"https:\/\/github.com\/brunerd\/plb\/blob\/main\/plb.min.sh\" target=\"_blank\" rel=\"noreferrer noopener\">plb.min.sh<\/a> a one-liner minified version, it&#8217;s still pretty big at 18k (but down from 36k for the fully commented version!). I can understand if you are embedding that function in your script, you might want something smaller.<\/p>\n\n\n\n<p><code><a href=\"https:\/\/github.com\/brunerd\/plx\" target=\"_blank\" rel=\"noreferrer noopener\">plx<\/a><\/code> has the <em>essential<\/em> feature that <code>plb<\/code> does: It can dive into nested data plists using the same colon delimited key paths and pipe characters that <code><code><a href=\"https:\/\/github.com\/brunerd\/plb\" target=\"_blank\" rel=\"noreferrer noopener\">plb<\/a><\/code><\/code> does, it just omits some of the fancy output that <code><code><a href=\"https:\/\/github.com\/brunerd\/plb\" target=\"_blank\" rel=\"noreferrer noopener\">plb<\/a><\/code><\/code> does but it has the most essential functions (it&#8217;s debatable they too could be removed but the functions were either small or necessary for other internal processes):<\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li><code>-l<\/code> for getting key names within a dictionary or the length of an array<\/li>\n\n\n\n<li>-t for getting the plist type of a node<\/li>\n\n\n\n<li>-e to convert ISO 8601 dates to epoch<\/li>\n<\/ul>\n\n\n\n<p>This gets <a href=\"https:\/\/github.com\/brunerd\/plx\/blob\/main\/plx.min.sh\" target=\"_blank\" rel=\"noreferrer noopener\">plx.min.sh<\/a> down below 10k! You might feel better about embedding in your script. Your call. Same easy syntax: <\/p>\n\n\n<div class=\"wp-block-image\">\n<figure class=\"aligncenter size-full is-resized\"><a href=\"https:\/\/www.brunerd.com\/blog\/wp-content\/uploads\/image-98.png\"><img loading=\"lazy\" decoding=\"async\" width=\"694\" height=\"60\" src=\"https:\/\/www.brunerd.com\/blog\/wp-content\/uploads\/image-98.png\" alt=\"\" class=\"wp-image-1658\" style=\"width:512px;height:auto\" srcset=\"https:\/\/www.brunerd.com\/blog\/wp-content\/uploads\/image-98.png 694w, https:\/\/www.brunerd.com\/blog\/wp-content\/uploads\/image-98-300x26.png 300w\" sizes=\"auto, (max-width: 694px) 100vw, 694px\" \/><\/a><figcaption class=\"wp-element-caption\"><code><a href=\"https:\/\/github.com\/brunerd\/plx\" target=\"_blank\" rel=\"noreferrer noopener\">plx<\/a><\/code>: All the output, half the bytes (embedded in your script)<\/figcaption><\/figure>\n<\/div>\n\n\n<h2 class=\"wp-block-heading\">Wrapping Up<\/h2>\n\n\n\n<p>Check them both out at the <a href=\"https:\/\/github.com\/brunerd\/plb\" target=\"_blank\" rel=\"noreferrer noopener\">plb GitHub repo<\/a> and the <a href=\"https:\/\/github.com\/brunerd\/plx\" target=\"_blank\" rel=\"noreferrer noopener\">plx GitHub repo<\/a>. They both have a full sized and commented shell script as well as a one-liner\/minified version. In their <strong>Releases<\/strong> (<a href=\"https:\/\/github.com\/brunerd\/plb\/releases\" data-type=\"link\" data-id=\"https:\/\/github.com\/brunerd\/plb\/releases\" target=\"_blank\" rel=\"noreferrer noopener\">plb<\/a>, <a href=\"https:\/\/github.com\/brunerd\/plx\/releases\" target=\"_blank\" rel=\"noreferrer noopener\">plx<\/a>) you&#8217;ll find a macOS package (pkg) download that will install the scripts to a self-named folder in <code>\/usr\/local\/<\/code> and then create a symlink to the main script <em>without<\/em> the <code>.sh<\/code> extension in <code>\/usr\/local\/bin<\/code> so you can run it with ease! I hope you get some use out of these. <\/p>\n\n\n\n<h2 class=\"wp-block-heading\">Homework<\/h2>\n\n\n\n<p>Make sure to check out the <strong>Advanced Examples<\/strong> in the <code>plb -h<\/code> help output (also included down below) for some simple loops that can crawl through <strong>all<\/strong> your user prefs, you might be surprised at what you find!<\/p>\n\n\n\n<pre class=\"wp-block-code language-bash\"><code>#plb Advanced Examples\n#zsh only, this ignores comment lines if you copy\/paste the examples\n set -k\n\n#add to this comma separated list of large preference domains to skip\nskipDomains=\"com.apple.audio.AudioComponentCache,com.apple.SpeakSelection,com.apple.garageband10,com.apple.EmojiCache,com.apple.audio.InfoHelper\"\n\n#1 Get key paths and values for all user preferences \n# substitute `plb -D` for ByHost user prefs\nIFS=$'\\n'; for domain in $(plb -d); do\n #skip large domains\n grep -qE '(^|,)'\"${domain}\"'(,|$)' &lt;&lt;&lt; \"${skipDomains}\" &amp;&amp; continue\n echo \"## Domain: $domain\"; echo \"## File Path: $(plb -f \"${domain}\")\"; plb -V \"${domain}\"; echo\ndone\n\n#2 Get key paths and values for all .plist files in \/Library\/Preferences\nIFS=$'\\n'; for domain in $(find \/Library\/Preferences -type f -name '*plist'); do\n grep -qE \"(^|,)${domain}(,|$)\" &lt;&lt;&lt; \"${skipDomains}\" &amp;&amp; continue\n echo \"## Domain: $domain\"; echo \"## File Path: $(plb -f \"${domain}\")\"\n # Adjust the info level\/output below\n plb -V \"${domain}\"; echo\ndone\n\n# Examples 1 &amp; 2 exercises\n # Adjust the info level\/output from -V to -CC or -C then try shallower crawls with -cc and -c\n # Output XML with `plb -x`, JSON with `plb -j`; both are speedier than crawling but with tradeoffs\n # `plb -p` prints quickly with PlistBuddy, while key\/value pairs are easily spotted, paths are not\n\n#3 Get the `file` type of all data nodes in your user prefs, you might find something interesting\nIFS=$'\\n'; for domain in $(plb -d); do\n grep -qE \"(^|,)${domain}(,|$)\" &lt;&lt;&lt; \"${skipDomains}\" &amp;&amp; continue\n echo \"## Domain: $domain\"\n echo \"## Path: $(plb -f \"${domain}\")\"\n #the sed at the end is if you use -CC, its removes the indents from the paths of data embedded plist\n for datapath in $(plb -cc \"${domain}\" | awk -F ' &gt;&gt;&gt; ' '\/data$\/ {print $1}' | sed 's\/^ *\/\/'); do \n  #get file type of data and key path\n  echo \"$(plb -F \"$domain\" \"$datapath\") &lt;&lt;&lt; ${datapath}\"\n done\n echo\ndone\n\n# Example 3 notes: \n # `plb -cc` does not go into data plists, use `plb -CC` to search within data plists\n # Use the awk field separator `-F ' &gt;&gt;&gt; '` to easily parse -cc\/-CC output ($1 paths, $2 type)\n<\/code><\/pre>\n","protected":false},"excerpt":{"rendered":"<p>Elevator pitch I&#8217;m excited to introduce two plist tools for macOS plb the plist broker and plx the plist extractor. The value of plb is its ability to visualize a plist as you will see below, just like my tool jpt does for JSON. plb can map out the complete paths and values that may [&hellip;]<\/p>\n","protected":false},"author":2,"featured_media":0,"comment_status":"closed","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"footnotes":""},"categories":[19,70,71,40,12,39],"tags":[20,66,69,67,68,24,23],"class_list":["post-1570","post","type-post","status-publish","format-standard","hentry","category-bash","category-plb","category-plx","category-projects","category-scripting","category-zsh","tag-bash","tag-plb","tag-plist","tag-plx","tag-sh","tag-shell","tag-zsh"],"_links":{"self":[{"href":"https:\/\/www.brunerd.com\/blog\/wp-json\/wp\/v2\/posts\/1570","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/www.brunerd.com\/blog\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/www.brunerd.com\/blog\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/www.brunerd.com\/blog\/wp-json\/wp\/v2\/users\/2"}],"replies":[{"embeddable":true,"href":"https:\/\/www.brunerd.com\/blog\/wp-json\/wp\/v2\/comments?post=1570"}],"version-history":[{"count":31,"href":"https:\/\/www.brunerd.com\/blog\/wp-json\/wp\/v2\/posts\/1570\/revisions"}],"predecessor-version":[{"id":1687,"href":"https:\/\/www.brunerd.com\/blog\/wp-json\/wp\/v2\/posts\/1570\/revisions\/1687"}],"wp:attachment":[{"href":"https:\/\/www.brunerd.com\/blog\/wp-json\/wp\/v2\/media?parent=1570"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/www.brunerd.com\/blog\/wp-json\/wp\/v2\/categories?post=1570"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/www.brunerd.com\/blog\/wp-json\/wp\/v2\/tags?post=1570"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}