Lessons from Swift 2: Processes, running commands, predicates, command tools, logs, playgrounds

Process (formerly NSTask)

Note the title change: code referring to NSTask won’t work, use Process instead. These generally work fine in Xcode Swift Playgrounds.

Calling a shell command simply:
let task = Process.launchedProcess(launchPath: path, arguments: params)
task.waitUntilExit() // waits for call to complete

You need to provide a string to the full path of the command, e.g. let path = "usr/bin/say"
and the arguments to that command as an array of strings (see below).

Calling a shell command and retrieving standard output:
let task = Process()
task.launchPath = path
task.arguments = params
let outPipe = Pipe()
task.standardOutput = outPipe // to capture standard error, use task.standardError = outPipe
let fileHandle = outPipe.fileHandleForReading
let data = fileHandle.readDataToEndOfFile()

Handle errors afterwards using:
let status = task.terminationStatus
if status != 0 {
// handle errors

Convert the returned data to a string using:
let theString = NSString(data: data, encoding: String.Encoding.utf8.rawValue)!

Remember that parameters are not passed through a command shell to expand abbreviations such as ~ for the Home folder. Each parameter must be complete and ready to hand to the command itself. To expand a path in the Home folder to a complete path:
let fileManager = FileManager.default
let fullPath = fileManager.homeDirectoryForCurrentUser.path + shortPath

Dividing verbs, options, and other parameters up into the array of strings passed can be tricky. The example:
var theListPar = ["list-keychains", "-d", "user"]
is used to represent the verb list-keychains and its option -d user.

To run a command with privileges using AppleScript, you can use:
var theScript = "do shell script \"softwareupdate --schedule\" with administrator privileges"
let appleScript = NSAppleScript(source: theScript)
let eventResult = appleScript?.executeAndReturnError(nil)

then handle the result along the lines of:
if (eventResult == nil) {
return "ERROR"
} else {
return (eventResult?.stringValue)!

When this is run, AppleScript will display the authentication dialog into which the user enters their admin password, and will then use that to obtain the privileges required to run the command.

Command predicates, using NSPredicate

let pred = NSPredicate(format: "%K == 'com.apple.TimeMachine'", argumentArray: ["subsystem"])
let string = pred.predicateFormat

will create a predicate subsystem == "com.apple.TimeMachine" correctly, when other methods often fail. Format options are fully documented in the docs.

How to run Swift code from the command line (HT Hector Matos)

1. Write standalone interface-free code in main.swift, with
as the opening line of the file.

2. Save main.swift, then in Terminal cd to that folder, and execute
chmod +x main.swift
to make it executable.

3. Run the code from the same folder using the command

Writing a (shell) command in Swift (HT Jean-Pierre Distler)

1. enum and function to parse options:
enum OptionType: String {
case debug = "b"
case defaultD = "d"
case error = "e"
case fault = "f"
case info = "i"
case unknown
init(value: String) {
switch value {
case "b": self = .debug
case "d": self = .defaultD
case "e": self = .error
case "f": self = .fault
case "i": self = .info
default: self = .unknown

func getOption(_ option: String) -> (option: OptionType, value: String) {
return (OptionType(value: option), option)

2. Then check argument count, parse first argument, and switch according to the option supplied:
let argCount = CommandLine.argc
if (argCount > 2) {
let first = CommandLine.arguments[1]
let second = CommandLine.arguments[2]
let (option, value) = getOption(first.substring(from: first.characters.index(first.startIndex, offsetBy: 1)))
switch option {
case .debug:
case .error:
case .fault:
case .info:
case .defaultD, .unknown:

Writing to Sierra’s new log

Easiest to implement as a class with static strings. By default, any normal strings are written out to log as <private>, which is unhelpful. The public formatting override below should bypass that, but at present doesn’t. However other format options such as Int do work currently.
import os.log
public class Demo {
static let gen_log = OSLog(subsystem: "co.mycompany.appname", category: "general")
public init() {
func writeLogEntry(type: OSLogType) {
os_log("Any static string", log: Demo.gen_log, type: type)
func writeLogEntry(type: OSLogType, number: Int) {
os_log("Here's an integer: %d", log: Demo.gen_log, type: type, number)
func writeLogEntry(type: OSLogType, string: String) {
os_log("This should write a string but ends up witholding the data as private: %{public}s", log: Demo.gen_log, type: type, string)

then call as
let myDemo = Demo()
myDemo.writeLogEntry(type: .debug, number: intParam!)


Swift Playgrounds (Xcode)

Any code which tries to create a new window is likely to be doomed. In many cases, Xcode will just freeze when you try to run it. If you need output, use text, or use a view within the Playground window, as shown in various examples.

Last tested on Xcode 8.2.1, Swift 3.0.2, macOS Sierra 10.12.3.