Get terminal output after a command swift

倖福魔咒の 提交于 2019-11-27 01:21:53
Martin R

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, one string for each line.

The first argument to runCommand() must be the full path to an executable, 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 found via the default search path. The disadvantage is that you have to quote/escape arguments correctly if they contain spaces or other characters 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)
}
Airspeed Velocity

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 Darwin

let 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 Darwin

let 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)

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!