I am working on a Swift app that interacts with external binaries (via NSTask) and returns the output as an array of strings, for each line.
When calling the executeCommand function multiple times, commands start failing to return expected data.
In the example Playground below I'm polling diskutil for volume names.
In the Debug Area the first response I get is:
Simply re-running the Playground results in:
Unable to obtain volume name
import Cocoa
func executeCommand(launchPath: String, arguments: [String], additionalDelay: Int=0) -> [String] {
let outputPipe = NSPipe()
var outputArray = [String]()
let task = NSTask()
task.launchPath = launchPath
task.arguments = arguments
task.standardOutput = outputPipe
outputPipe.fileHandleForReading.waitForDataInBackgroundAndNotify()
NSNotificationCenter.defaultCenter().addObserverForName(NSFileHandleDataAvailableNotification, object: outputPipe.fileHandleForReading , queue: nil) {
notification in
let output = outputPipe.fileHandleForReading.availableData
let outputString = String(data: output, encoding: NSUTF8StringEncoding) ?? ""
outputArray = outputString.componentsSeparatedByCharactersInSet(.newlineCharacterSet())
outputPipe.fileHandleForReading.waitForDataInBackgroundAndNotify()
}
task.launch()
sleep(UInt32(additionalDelay)) // Additional delay
task.waitUntilExit()
// Remove last empty line from output array.
if outputArray.count > 0 {
outputArray.removeLast()
}
return outputArray
}
// Example usage
// Output volume name from provided disk identifier
func volumeName(identifier: String) -> String {
let volumeNameCommand = "/usr/sbin/diskutil info \(identifier) | awk '/Volume Name:/' | sed 's/Volume Name://g'| sed -e 's/^[[:space:]]*//' -e 's/[[:space:]]*$//'"
let volumeNameOutput = executeCommand("/bin/bash", arguments: ["-c", volumeNameCommand])
var volumeName = "Unable to obtain volume name"
if volumeNameOutput.count > 0 {
volumeName = volumeNameOutput[0]
}
return volumeName
}
let identifiers = ["/dev/disk0","/dev/disk1","/dev/disk2","/dev/disk3","/dev/disk4"]
for identifier in identifiers {
print(volumeName(identifier))
}
I really need consistent results, but don't understand where I'm going wrong.
Any help would be greatly appreciated!
I believe the asynchronous execution of tasks was causing the inconsistent results. Below is a new function working as expected:
func executeCommand(launchPath: String, arguments: [String]) -> [String] {
let task: NSTask = NSTask()
let pipe: NSPipe = NSPipe()
task.launchPath = launchPath
task.arguments = arguments
task.standardOutput = pipe
task.launch()
task.waitUntilExit()
let handle = pipe.fileHandleForReading
let data = handle.readDataToEndOfFile()
let outputString = String(data: data, encoding: NSUTF8StringEncoding) ?? ""
var outputArray = [String]()
outputArray = outputString.componentsSeparatedByCharactersInSet(.newlineCharacterSet())
return outputArray
}