For my day job, I write macOS management agent software for LANDESK. A significant part of this job is gathering the current state of Apple Macintosh Computers. Apple provides a system configuration framework that can provide much of the information I need. That framework has "documentation" that can be a challenge to decode. Many times, for quick prototypes, I will call out to the
system_profiler
command line tool. It gives quick feedback and you can easily see the layout of the information you are dealing with.
Apple's new Swift language has some great features that make calling external programs and digging through their output safe and relatively easy. In this post and in several to come, I will show you how to do this. Disclaimer: I would not consider this "best practices." The code in these posts was written while I was first learning Swift myself and is the result of several quick prototypes. Still, I think it is a decent example of using Swift to run external programs.
Running
/usr/sbin/system_profiler
will give you a whole lot of information about your Mac. In fact, everything you can get from the "System Information" app shows up in the text output of system_profiler
. Since some of this information is gathered every time system_profiler
is run, it can take a long time. Fortunately, we can ask system_profiler
to just give us information on a specific sub-system by specifying a data type. For instance, we can get just network information or general information about our hardware. Running system_profiler SPHardwareDataType
would give us this:
Hardware:
Hardware Overview:
Model Name: iMac
Model Identifier: iMac15,1
Processor Name: Intel Core i7
Processor Speed: 4 GHz
Number of Processors: 1
Total Number of Cores: 4
L2 Cache (per Core): 256 KB
L3 Cache: 8 MB
Memory: 32 GB
Boot ROM Version: IM151.0207.B06
SMC Version (system): 2.22f16
Serial Number (system): C02NN4G6FY14
Hardware UUID: D402F211-62D7-59CA-84D0-F887EFF089DC
You can get a list of data types by running
system_profiler -listDataTypes
. Running system_profiler
from Swift is simple. Swift has a Process class that will take care of the work of starting and managing an external task. The following launchAndGetText function can be copied and pasted directly into an Xcode playground. It will run a shell command found at path
with the arguments found in the String array args
and return the resulting output in a String
. If no text output was generated by the command at path
, it returns an empty string.
This function is pretty straightforward. Lines 2 through 4 set up the basic process object. The
launchPath
property on the process object "ps" is just the path of the program we want to run. I like to pass the full path, not just the program name, primarily for security reasons but also because I don't like to rely on the $PATH environment variable. The arguments
property is just an array of strings that will be passed as command line arguments. If you have done any C programming for UNIX, you might be wondering about arg[0] and such. Don't. Just put the arguments to the program, not the name of the program itself into the arguments array. You can look at the example later in the post if this isn't making sense.
Lines 6 and 7 attach a Pipe to the Process object's standard out stream. This Pipe lets you read the bytes coming out of the running processes standard out stream. So anything the process normally prints to the terminal is available for your program to read. You can also hook up pipes to standard error to get error messages and standard in if you want to send data to the process. Lines 9 and 10 launch the process and wait for it to finish. There are ways to get the termination status from the process. We will take a look at that another time.
The gist that follows shows an example of using
launchAndGetText
to get the hardware UUID from a Mac.
You can see on lines 1 and 2 that we are calling
system_profiler
with a single command line argument, SPHardwareDataType
. This will return the text output from system_profiler
as a String. We can use the Swift String's component
method to break the text into lines of text in an array of Strings. Finding the UUID is then a simple matter of walking through the array and looking for the string "Hardware UUID". Swift provides several ways of digging through arrays and I will spend several posts talking about them in the future. For now I just use the for in
loop that is similar to what you would find in most programming languages.
This approach of grabbing the plain text output of
system_profiler
works great for simple information like the text we get using the SPHardwareDataType
argument. Not all everything we might want to get out of system_profiler
is that simple. For example, the output of SPNetworkDataType
is hierarchical with a level of interfaces, each interface having various bits of information like interface names, addresses, proxy information, and so on. Here is an example of what you get for two interfaces:
Wi-Fi:
Type: AirPort
Hardware: AirPort
BSD Device Name: en0
IPv4 Addresses: 192.168.0.4
IPv4:
AdditionalRoutes:
DestinationAddress: 192.168.0.4
SubnetMask: 255.255.255.255
Addresses: 192.168.0.4
ARPResolvedHardwareAddress: 58:8b:f3:ae:9d:c9
ARPResolvedIPAddress: 192.168.0.1
Configuration Method: DHCP
ConfirmedInterfaceName: en0
Interface Name: en0
Network Signature: IPv4.Router=192.168.0.1;IPv4.RouterHardwareAddress=58:8b:f3:ae:9d:c9
Router: 192.168.0.1
Subnet Masks: 255.255.255.0
IPv6:
Configuration Method: Automatic
DNS:
Domain Name: Home
Server Addresses: 192.168.0.1, 205.171.3.25
DHCP Server Responses:
Domain Name: Home
Domain Name Servers: 192.168.0.1,205.171.3.25
Lease Duration (seconds): 0
DHCP Message Type: 0x05
Routers: 192.168.0.1
Server Identifier: 192.168.0.1
Subnet Mask: 255.255.255.0
Ethernet:
MAC Address: 20:c9:d0:44:0e:a1
Media Options:
Media Subtype: Auto Select
Service Order: 1
This is a small subset of the network information that comes out of
system_profiler
. You get all this and more for each interface on your computer. Finding the MAC address for a specific interface becomes complicated using this unstructured output. Fortunately, system_profiler
will also output information in Apple's plist format and Swift knows exactly what to do with that. In my next post, we will take a look at taking XML from system_profiler
and putting it into Swift data structures that can be easily navigated.