<p><div><div><div><div data-show-faces="false" data-height="21" data-width="100" data-layout="button_count" data-send="true" data-href="https://www.r-bloggers.com/friday-rstats-twofer-finding-macos-32-bit-apps-processing-data-from-system-commands/"></div></div><div></div></div><div></div></div>
<div>
(This article was first published on <strong><a href="https://rud.is/b/2018/08/24/friday-rstats-twofer-finding-macos-32-bit-apps-processing-data-from-system-commands/"> R – rud.is</a></strong>, and kindly contributed to <a rel="nofollow" href="https://www.r-bloggers.com/">R-bloggers)</a> </div>
<p>Apple has <a rel="nofollow" href="https://support.apple.com/en-us/HT208436" target="_blank">run the death bell</a> on 32-bit macOS apps and, if you’re running a recent macOS version on your Mac (which you <em>should</em> so you can get security updates) you likely see this alert from time-to-time:</p>
<p><a rel="nofollow" href="https://rud.is/b/2018/08/24/friday-rstats-twofer-finding-macos-32-bit-apps-processing-data-from-system-commands/macos-high-sierra-32-bit-app-alert/" target="_blank"><img data-orig-file="https://i1.wp.com/rud.is/b/wp-content/uploads/2018/08/macos-high-sierra-32-bit-app-alert.jpg?fit=840%2C314&amp;ssl=1" data-orig-size="840,314" data-medium-file="https://i1.wp.com/rud.is/b/wp-content/uploads/2018/08/macos-high-sierra-32-bit-app-alert.jpg?fit=300%2C112&amp;ssl=1" data-attachment-id="11428" data-image-meta="{" data-image-description="" src="https://i1.wp.com/rud.is/b/wp-content/uploads/2018/08/macos-high-sierra-32-bit-app-alert.jpg?resize=780%2C292&ssl=1" data-permalink="https://rud.is/b/2018/08/24/friday-rstats-twofer-finding-macos-32-bit-apps-processing-data-from-system-commands/macos-high-sierra-32-bit-app-alert/" data-large-file="https://i1.wp.com/rud.is/b/wp-content/uploads/2018/08/macos-high-sierra-32-bit-app-alert.jpg?fit=530%2C198&amp;ssl=1" alt="" data-image-title="macos-high-sierra-32-bit-app-alert" width="450" data-comments-opened="1" class="wp-image-11428" data-recalc-dims="1"></a></p>
<p>If you’re like me, you click through that and keep working but later ponder just how many of those apps you have. They are definitely going away, so knowing if your favourite app is on the chopping block is likely a good idea.</p>
<p>You can get this information via the “About This Mac”Í¢”System Report” app and sorting via one of the table fields.</p>
<p>R folks are data folks and we know we can do better than that. But, first we need to get the data. Thankfully, we can get this via the <a rel="nofollow" href="https://ss64.com/osx/system_profiler.html" target="_blank"><code>system_profiler</code></a> command-line utility since it can both display user-friendly information in the terminal and also generate an XML version of the information to work with. We won’t need to head to the terminal for this work, though, since there are many ways to execute the command from R and read the generated output.</p>
<h3>Executing System Calls from R</h3>
<p>Base R provides two core methods for issuing a system call:</p>
<ul>
<li><code>system()</code></li>
<li><code>system2()</code></li>
</ul>
<p>Note that there are other functions provided with a base R installation that can also issue system commands and process the “piped” output, but we’ll focus on these deliberate invocation ones.</p>
<p>Functions in two other packages can also assist with this task and we’ll include a look at:</p>
<ul>
<li><code>processx::run()</code></li>
<li><code>sys::exec_internal()</code></li>
</ul>
<p>as well.</p>
<p>Why leave base R for this task? Truthfully, we really don’t <em>need</em> to, but both <code>sys</code> and <code>processx</code> have other tasks which do make them handy tools in your package toolbox. Having said that, keep reading since we’re going to end up <em>not</em> choosing the built-in functions for this task.</p>
<p>This is the command line we need to execute:</p>
<pre><code>system_profiler -xml -detailLevel full SPApplicationsDataType
</code></pre>
<p>Let’s load up all the packages we’ll be needing and execute this command-line all four ways, then briefly discuss the differences:</p>
<pre><code>library(sys)
library(processx)
library(microbenchmark)
library(xml2)
library(tidyverse)

system(
  command = &quot;system_profiler -xml -detailLevel full SPApplicationsDataType&quot;,
  intern = TRUE
) -> apps_system

str(apps_system)
##  chr [1:10665] &quot;&quot; ...

system2(
  command = &quot;system_profiler&quot;,
  args = c(&quot;-xml&quot;, &quot;-detailLevel&quot;, &quot;full&quot;, &quot;SPApplicationsDataType&quot;),
  stdout = TRUE
) -> apps_system2

str(apps_system2)
##  chr [1:10665] &quot;&quot; ...

processx::run(
  command = &quot;system_profiler&quot;,
  args = c(&quot;-xml&quot;, &quot;-detailLevel&quot;, &quot;full&quot;, &quot;SPApplicationsDataType&quot;),
  spinner = TRUE
) -> apps_processx_run

str(apps_processx_run)
## List of 4
##  $ status : int 0
##  $ stdout : chr &quot;XML STRING THAT prism.js won't let me show&quot;
##  $ stderr : chr &quot;&quot;
##  $ timeout: logi FALSE

sys::exec_internal(
  cmd = &quot;system_profiler&quot;,
  args = c(&quot;-xml&quot;, &quot;-detailLevel&quot;, &quot;full&quot;, &quot;SPApplicationsDataType&quot;)
) -> apps_sys_exec_internal

str(apps_sys_exec_internal)
## List of 3
##  $ status: int 0
##  $ stdout: raw [1:331133] 3c 3f 78 6d ...
##  $ stderr: raw(0) </code></pre>
<p>The core difference between <code>system()</code> and the rest is that <em>you</em> need to <a rel="nofollow" href="https://stat.ethz.ch/R-manual/R-devel/library/base/html/shQuote.html" target="_blank"><code>shQuote()</code><img src="https://i1.wp.com/s.w.org/images/core/emoji/11/72x72/1f517.png?w=456&ssl=1" alt="ðŸ" class="wp-smiley" data-recalc-dims="1"></a> for <code>system()</code> whereas that’s taken care of for you by the others (so they’re a bit safer by default since you’re more than likely going to forget to <code>shQuote()</code>).</p>
<p>You can definitely notice the main differences in return objects. The built-in functions just give us the character data from the standard output stream (<code>stdout</code>) and the last two return a more structured object that provides more explicit information about the job we just executed. The base ones can provide this detail, but it’s a twisty maze of remembering which options do what vs the more (IMO) straightforward approach both <code>processx</code> and <code>sys</code> take.</p>
<p>You’ll also notice a difference in <code>stdout</code> between <code>processx</code> and <code>sys</code> with the latter giving us a raw vector vs a character vector. This gives us a great deal of power and flexibility. It also turns out to be a great choice for processing command-line-generated XML data. Here’s why:</p>
<pre><code>microbenchmark(
  sys = xml2::read_xml(apps_sys_exec_internal$stdout),
  processx = xml2::read_xml(apps_processx_run$stdout)
)
## Unit: milliseconds
##      expr      min       lq      mean    median        uq      max neval
##       sys 4.086492  4.60078  9.085143  5.508814  5.906942 207.6495   100
##  processx 9.510356 10.98282 14.275499 12.054810 13.292234 163.9870   100</code></pre>
<p>It turns out <code>xml2::read_xml()</code> makes much quicker work of the raw vector data (though, I mean, <em>really</em>—are we really going to care about those ~5ms IRL?).</p>
<p>We’ll move on to the real reason for the post, but definitely explore both <code>sys</code> and <code>processx</code> since they are both super-handy packages.</p>
<h3><em>“Can we please just find the 32-bit apps already?”</em></h3>
<p>No problem. Well, actually, there is a minor annoyance. These are <a rel="nofollow" href="https://developer.apple.com/library/archive/documentation/Cocoa/Conceptual/PropertyLists/UnderstandXMLPlist/UnderstandXMLPlist.html" target="_blank">property list XML files</a> and I’ll confess that I truly hate this format. There are “dictionary arrays” of <code>key</code> and <code>value</code> nodes, but those nodes are siblings vs directly associated pairs. So, we have to use the sibling relationship to work with them. It’s not <em>hard</em>, per se, just (again, IMO) suboptimal.</p>
<p>Let’s take a look at it:</p>
<pre><code>apps &lt;- read_xml(apps_sys_exec_internal$stdout)

xml_find_all(apps, &quot;//array/dict&quot;)
## {xml_nodeset (476)}
##  [1] </code></pre>