Get terminal output after a command swift Get terminal output after a command swift swift swift

Get terminal output after a command swift


NSTask is class to run another program as a subprocess. You can capture the program's output, error output, exit status and much more.

Expanding on my answer to xcode 6 swift system() command,here is a simple utility function to run a command synchronously,and return the output, error output and exit code (now updated for Swift 2):

func runCommand(cmd : String, args : String...) -> (output: [String], error: [String], exitCode: Int32) {    var output : [String] = []    var error : [String] = []    let task = NSTask()    task.launchPath = cmd    task.arguments = args    let outpipe = NSPipe()    task.standardOutput = outpipe    let errpipe = NSPipe()    task.standardError = errpipe    task.launch()    let outdata = outpipe.fileHandleForReading.readDataToEndOfFile()    if var string = String.fromCString(UnsafePointer(outdata.bytes)) {        string = string.stringByTrimmingCharactersInSet(NSCharacterSet.newlineCharacterSet())        output = string.componentsSeparatedByString("\n")    }    let errdata = errpipe.fileHandleForReading.readDataToEndOfFile()    if var string = String.fromCString(UnsafePointer(errdata.bytes)) {        string = string.stringByTrimmingCharactersInSet(NSCharacterSet.newlineCharacterSet())        error = string.componentsSeparatedByString("\n")    }    task.waitUntilExit()    let status = task.terminationStatus    return (output, error, status)}

Sample usage:

let (output, error, status) = runCommand("/usr/bin/git", args: "status")print("program exited with status \(status)")if output.count > 0 {    print("program output:")    print(output)}if error.count > 0 {    print("error output:")    print(error)}

Or, if you are only interested in the output, but not in the error messages or exit code:

let output = runCommand("/usr/bin/git", args: "status").output

Output and error output are returned as an array of strings, onestring for each line.

The first argument to runCommand() must be the full path to anexecutable, such as "/usr/bin/git". You can start the program using a shell (which is what system() also does):

let (output, error, status) = runCommand("/bin/sh", args: "-c", "git status")

The advantage is that the "git" executable is automatically foundvia the default search path. The disadvantage is that you have toquote/escape arguments correctly if they contain spaces or othercharacters which have a special meaning in the shell.


Update for Swift 3:

func runCommand(cmd : String, args : String...) -> (output: [String], error: [String], exitCode: Int32) {    var output : [String] = []    var error : [String] = []    let task = Process()    task.launchPath = cmd    task.arguments = args    let outpipe = Pipe()    task.standardOutput = outpipe    let errpipe = Pipe()    task.standardError = errpipe    task.launch()    let outdata = outpipe.fileHandleForReading.readDataToEndOfFile()    if var string = String(data: outdata, encoding: .utf8) {        string = string.trimmingCharacters(in: .newlines)        output = string.components(separatedBy: "\n")    }    let errdata = errpipe.fileHandleForReading.readDataToEndOfFile()    if var string = String(data: errdata, encoding: .utf8) {        string = string.trimmingCharacters(in: .newlines)        error = string.components(separatedBy: "\n")    }    task.waitUntilExit()    let status = task.terminationStatus    return (output, error, status)}


system spawns a new process so you can’t capture its ouput. The equivalent that gives you a way to do this would be popen, which you could use like this:

import Darwinlet fp = popen("ping -c 4 localhost", "r")var buf = Array<CChar>(count: 128, repeatedValue: 0)while fgets(&buf, CInt(buf.count), fp) != nil,      let str = String.fromCString(buf) {    print(str)}fclose(fp)

However, don’t do it this way. Use NSTask as Martin describes.

edit: based on your request to run multiple commands in parallel, here is some probably-unwise code:

import Darwinlet commands = [    "tail /etc/hosts",    "ping -c 2 localhost",]let fps = commands.map { popen($0, "r") }var buf = Array<CChar>(count: 128, repeatedValue: 0)let results: [String] = fps.map { fp  in    var result = ""    while fgets(&buf, CInt(buf.count), fp) != nil,          let str = String.fromCString(buf) {        result += str    }    return result}fps.map { fclose($0) }println("\n\n----\n\n".join(map(zip(commands,results)) { "\($0):\n\($1)" }))

(seriously, use NSTask)


my 2 cents for swift 5.x , macOS with call back, invoked when done.

final func doTaskFor(cmd: String, arguments: [String], callback: CallBackWithStr = nil){let task = Process()let absolutePath = <add your specific path..> let fullCmd = absolutePath+cmd#if DEBUG// used to debug.let debugstr :String = fullCmd + " " + arguments.oneLine()print(debugstr)#endiftask.executableURL = URL(fileURLWithPath: fullCmd)task.arguments = arguments// Create 2 Pipes and make the tasklet outPipe = Pipe()task.standardOutput = outPipelet errPipe = Pipe()task.standardError = errPipetask.terminationHandler = { (process) in        print("\ndidFinish: \(!process.isRunning)")        // Get the data    let outData = outPipe.fileHandleForReading.readDataToEndOfFile()    let output = String(data: outData, encoding: .utf8)    // print(output!)        // Get the error    let errData = errPipe.fileHandleForReading.readDataToEndOfFile()    let err = String(data: errData, encoding: .utf8)    // print(err!)        // usually output is empty if error.        callback?(output ?? "")    }do {    try task.run()} catch {    let msg = " \(error)"    Log(msg: msg, safe: true)    print(msg)    }

}