Many Mac users don’t use Terminal’s command line, and even when they may be happy to do so, running a complex series of commands isn’t at all popular. Several of the free tools in the Downloads page here are, in essence, wrapped up shell commands. Sometimes there’s no alternative: to access the unified log, Apple provides one less than useful GUI tool, Console, and the command log
.
Wrapping up commands like this is a very good reason for ‘scripting’. With the slow death by neglect of AppleScript, I have switched to using Swift. This also gives fairly ready access to a whole load of features which are much tougher to handle in AppleScript: most log excerpts that I obtain in my apps are in JSON format, which I can then parse more powerfully into styled and formatted text for the user, for instance.
Calling a command tool in Swift has been quite straightforward, even when using a background thread to avoid the app displaying a spinning beachball. For example, running the log
command can be done with this:
processingQueue.addOperation {
let task = Process()
task.launchPath = "/usr/bin/log"
task.arguments = theFullCmdStr
let outPipe = Pipe()
task.standardOutput = outPipe
task.launch()
let fileHandle = outPipe.fileHandleForReading
let data = fileHandle.readDataToEndOfFile()
task.waitUntilExit()
let status = task.terminationStatus
OperationQueue.main.addOperation {
if (status != 0) {
self.replaceOutputText(string: "log show command returned error number \(status)")
} else {
self.ParseLog(from: data)
}
self.progressSpinner.stopAnimation(self)
self.progressSpinner.isHidden = true
}}
This uses the Process class, and has served me and my tools well from El Capitan on. But the documentation now warns me that two features which that relies on – Process.launchPath and Process.launch() – are now deprecated, and shouldn’t be used in Mojave, although both still seem to work fine. Presuming that they are likely to be removed in macOS 10.15, now is the time for me to fix this.
Process.launchPath has been effectively replaced by Process.executableURL, which simply takes a URL to the process rather than a simple path. So instead of writing
task.launchPath = "/usr/bin/log"
I now need
task.executableURL = URL(fileURLWithPath: "/usr/bin/log")
The only snag with this is that it isn’t available until macOS 10.13, so apps which run on Sierra and earlier still need to use the old form.
Process.launch() is more complex to replace, as its substitute (which is also not available in Sierra or earlier) is Process.run(). Like Process.executableURL it is essentially undocumented, except to make clear that it throws, unlike Process.launch(), so the code above has to be recast to handle that. Testing shows that Process.run() doesn’t throw if the command called returns an error, which would have been a useful reason for it to throw. So the code still has to handle command errors, as well as catching a throw.
My substitute therefore becomes:
processingQueue.addOperation {
let task = Process()
task.executableURL = URL(fileURLWithPath: "/usr/bin/log")
task.arguments = theFullCmdStr
let outPipe = Pipe()
task.standardOutput = outPipe
do {
try task.run()
} catch {
OperationQueue.main.addOperation {
self.replaceOutputText(string: "log show command failed\n")
self.progressSpinner.stopAnimation(self)
self.progressSpinner.isHidden = true
}}
let fileHandle = outPipe.fileHandleForReading
let data = fileHandle.readDataToEndOfFile()
task.waitUntilExit()
let status = task.terminationStatus
OperationQueue.main.addOperation {
if (status != 0) {
self.replaceOutputText(string: "log show command returned error number \(status)")
} else {
self.ParseLog(from: data)
}
self.progressSpinner.stopAnimation(self)
self.progressSpinner.isHidden = true
}}
The snag with this change is that using Process.executableURL and Process.run() isn’t supported prior to macOS 10.13. As most of my apps support Sierra, and many El Capitan too, two separate routines are required, conditional on if #available(OSX 10.14, *)
. High Sierra can use either, but there have been reports that calling Process.run() leaks a Mach port each time, so I’ll keep 10.13 using the old calls, thank you.
I don’t think that I have any apps which are likely to make many calls and suffer significant Mach port leakage. If you do, it’s worth taking a careful look to see if your app is affected (thanks to granada29 for commenting on this).
I’m sure that Apple has good reasons for these deprecations, but these small changes have doubled the amount of code required to run a command, which isn’t good, is it?